web-dev-qa-db-fra.com

Comment supprimer efficacement (performance) de nombreux éléments de List en Java?

J'ai une liste assez importante d'éléments nommés (> = 1 000 000 d'éléments) et une condition indiquée par <cond> qui sélectionne les éléments à supprimer et <cond> est vraie pour beaucoup (peut-être la moitié) d'éléments de ma liste.

Mon objectif est de supprimer efficacement les éléments sélectionnés par <cond> et de conserver tous les autres éléments. La liste source peut être modifiée, une nouvelle liste peut être créée - la meilleure façon de le faire doit être choisie en fonction des performances.

Voici mon code de test:

    System.out.println("preparing items");
    List<Integer> items = new ArrayList<Integer>(); // Integer is for demo
    for (int i = 0; i < 1000000; i++) {
        items.add(i * 3); // just for demo
    }

    System.out.println("deleting items");
    long startMillis = System.currentTimeMillis();
    items = removeMany(items);
    long endMillis = System.currentTimeMillis();

    System.out.println("after remove: items.size=" + items.size() + 
            " and it took " + (endMillis - startMillis) + " milli(s)");

et mise en œuvre naïve:

public static <T> List<T> removeMany(List<T> items) {
    int i = 0;
    Iterator<T> iter = items.iterator();
    while (iter.hasNext()) {
        T item = iter.next();
        // <cond> goes here
        if (/*<cond>: */i % 2 == 0) {
            iter.remove();
        }
        i++;
    }
    return items;
}

Comme vous pouvez le constater, j'ai utilisé l'item item modulo 2 == 0 comme condition de suppression (<cond>) - uniquement à des fins de démonstration.

Quelle meilleure version de removeMany peut être fournie et pourquoi cette meilleure version est réellement meilleure?

29
WildWezyr

OK, il est temps de tester les résultats des approches proposées. Voici les approches que j'ai testées (le nom de chaque approche est également le nom de la classe dans mes sources):

  1. NaiveRemoveManyPerformer - ArrayList avec itérateur et remove - première et naïve implémentation donnée dans ma question.
  2. BetterNaiveRemoveManyPerformer - ArrayList avec itération en arrière et retrait d'un bout à l'autre.
  3. LinkedRemoveManyPerformer - itérateur naïf et supprimer, mais en travaillant sur LinkedList. Disadventage: ne fonctionne que pour LinkedList.
  4. CreateNewRemoveManyPerformer - ArrayList est créé sous forme de copie (seuls les éléments conservés sont ajoutés), l'itérateur est utilisé pour parcourir l'entrée ArrayList.
  5. SmartCreateNewRemoveManyPerformer - mieux CreateNewRemoveManyPerformer - la taille initiale (capacité) du résultat ArrayList est définie sur la taille de la liste finale. Inconvénient: vous devez connaître la taille finale de la liste au début.
  6. FasterSmartCreateNewRemoveManyPerformer - encore meilleur (?) SmartCreateNewRemoveManyPerformer - utilisez l'index des éléments (items.get(idx)) au lieu de l'itérateur.
  7. MagicRemoveManyPerformer - fonctionne sur place (pas de copie de liste) pour ArrayList et compacte les trous (éléments supprimés) à partir des éléments commençant à la fin de la liste. Disadventage: cette approche change l'ordre des éléments dans la liste.
  8. ForwardInPlaceRemoveManyPerformer - fonctionne pour ArrayList - déplace les éléments de rétention pour combler les trous, enfin la sous-liste est renvoyée (pas de suppression finale ni d'effacement).
  9. GuavaArrayListRemoveManyPerformer - Google Guava Iterables.removeIf utilisé pour ArrayList - presque identique à ForwardInPlaceRemoveManyPerformer mais effectue la suppression finale des éléments à la fin de la liste.

Le code source complet est donné à la fin de cette réponse.

Les tests ont été réalisés avec différentes tailles de liste (de 10 000 à 10 000 000 d'éléments) et différents facteurs de suppression (spécifiant le nombre d'éléments à supprimer de la liste).

Comme je l'ai posté ici dans les commentaires pour d'autres réponses - j'ai pensé que copier des éléments de ArrayList à seconde ArrayList serait plus rapide que d'itérer LinkedList et de simplement supprimer des éléments. La documentation Java de Sun indique que le facteur constant de ArrayList est faible comparé à celui de l'implémentation LinkedList, mais étonnamment, ce n'est pas le cas dans mon problème.

Dans la pratique, LinkedList avec une simple itération et suppression a la meilleure performance dans la plupart des cas (cette approche est implémentée dans LinkedRemoveManyPerformer). Habituellement, seule la performance MagicRemoveManyPerformer est comparable à LinkedRemoveManyPerformer, les autres approches étant nettement plus lentes. Google Guava GuavaArrayListRemoveManyPerformer est plus lent que le code similaire créé à la main (car mon code ne supprime pas les éléments inutiles en fin de liste).

Exemple de résultats pour supprimer 500 000 articles sur 1 000 000 articles source:

  1. NaiveRemoveManyPerformer: test non effectué - Je ne suis pas ce patient, mais son rendement est pire que BetterNaiveRemoveManyPerformer.
  2. BetterNaiveRemoveManyPerformer: 226080 milli (s)
  3. LinkedRemoveManyPerformer: 69 milli (s)
  4. CreateNewRemoveManyPerformer: 246 milli (s)
  5. SmartCreateNewRemoveManyPerformer: 112 milli (s)
  6. FasterSmartCreateNewRemoveManyPerformer: 202 milli (s)
  7. MagicRemoveManyPerformer: 74 milli (s)
  8. ForwardInPlaceRemoveManyPerformer: 69 milli (s)
  9. GuavaArrayListRemoveManyPerformer: 118 milli (s)

Exemple de résultats pour supprimer 1 élément sur 1 000 000 d'éléments source (le premier élément est supprimé):

  1. BetterNaiveRemoveManyPerformer: 34 milli (s)
  2. LinkedRemoveManyPerformer: 41 milli (s)
  3. CreateNewRemoveManyPerformer: 253 milli (s)
  4. SmartCreateNewRemoveManyPerformer: 108 milli (s)
  5. FasterSmartCreateNewRemoveManyPerformer: 71 milli (s)
  6. MagicRemoveManyPerformer: 43 milli (s)
  7. ForwardInPlaceRemoveManyPerformer: 73 milli (s)
  8. GuavaArrayListRemoveManyPerformer: 78 milli (s)

Exemple de suppression de 333 334 articles d'un million d'articles sources:

  1. BetterNaiveRemoveManyPerformer: 253206 milli (s)
  2. LinkedRemoveManyPerformer: 69 milli (s)
  3. CreateNewRemoveManyPerformer: 245 milli (s)
  4. SmartCreateNewRemoveManyPerformer: 111 milli (s)
  5. FasterSmartCreateNewRemoveManyPerformer: 203 milli (s)
  6. MagicRemoveManyPerformer: 69 milli (s)
  7. ForwardInPlaceRemoveManyPerformer: 72 milli (s)
  8. GuavaArrayListRemoveManyPerformer: 102 milli (s)

Exemples de résultats pour la suppression de 1 000 000 (tous) éléments de 1 000 000 (tous les éléments sont supprimés, mais avec un traitement un par un. Si vous savez a priori que tous les éléments doivent être supprimés, la liste doit être simplement effacée):

  1. BetterNaiveRemoveManyPerformer: 58 milli (s)
  2. LinkedRemoveManyPerformer: 88 milli (s)
  3. CreateNewRemoveManyPerformer: 95 milli (s)
  4. SmartCreateNewRemoveManyPerformer: 91 milli (s)
  5. FasterSmartCreateNewRemoveManyPerformer: 48 milli (s)
  6. MagicRemoveManyPerformer: 61 milli (s)
  7. ForwardInPlaceRemoveManyPerformer: 49 milli (s)
  8. GuavaArrayListRemoveManyPerformer: 133 milli (s)

Mes conclusions finales: utiliser une approche hybride - si vous utilisez LinkedList - l'itération et la suppression simples est préférable, si vous utilisez ArrayList - cela dépend si l'ordre des éléments est important - utilisez ForwardInPlaceRemoveManyPerformer puis, si l'ordre des éléments peut être modifié - le meilleur choix est MagicRemoveManyPerformer. Si le facteur de suppression est connu a priori (vous savez combien d'éléments seront supprimés par rapport aux éléments conservés), d'autres conditions conditionnelles pourront alors être appliquées pour choisir une approche plus performante dans des situations particulières. Mais le facteur de suppression connu n’est pas un cas habituel ... Google Guava Iterables.removeIf est une solution hybride, mais avec une hypothèse légèrement différente (la liste originale doit être changée, une nouvelle ne peut pas être créée et l’ordre des articles compte toujours) - ce sont les hypothèses les plus courantes, donc removeIf est le meilleur choix dans la plupart des cas réels.

Notez également que toutes les bonnes approches (naïf n'est pas bon!) Sont suffisantes. Chacune d'entre elles devrait faire l'affaire, mais une approche naïve doit être évitée.

Enfin - mon code source pour les tests.

package WildWezyrListRemovalTesting;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import Java.util.ArrayList;
import Java.util.Iterator;
import Java.util.LinkedList;
import Java.util.List;

public class RemoveManyFromList {

    public static abstract class BaseRemoveManyPerformer {

        protected String performerName() {
            return getClass().getSimpleName();
        }

        protected void info(String msg) {
            System.out.println(performerName() + ": " + msg);
        }

        protected void populateList(List<Integer> items, int itemCnt) {
            for (int i = 0; i < itemCnt; i++) {
                items.add(i);
            }
        }

        protected boolean mustRemoveItem(Integer itemVal, int itemIdx, int removeFactor) {
            if (removeFactor == 0) {
                return false;
            }
            return itemIdx % removeFactor == 0;
        }

        protected abstract List<Integer> removeItems(List<Integer> items, int removeFactor);

        protected abstract List<Integer> createInitialList();

        public void testMe(int itemCnt, int removeFactor) {
            List<Integer> items = createInitialList();
            populateList(items, itemCnt);
            long startMillis = System.currentTimeMillis();
            items = removeItems(items, removeFactor);
            long endMillis = System.currentTimeMillis();
            int chksum = 0;
            for (Integer item : items) {
                chksum += item;
            }
            info("removing took " + (endMillis - startMillis)
                    + " milli(s), itemCnt=" + itemCnt
                    + ", removed items: " + (itemCnt - items.size())
                    + ", remaining items: " + items.size()
                    + ", checksum: " + chksum);
        }
    }
    private List<BaseRemoveManyPerformer> rmps =
            new ArrayList<BaseRemoveManyPerformer>();

    public void addPerformer(BaseRemoveManyPerformer rmp) {
        rmps.add(rmp);
    }
    private Runtime runtime = Runtime.getRuntime();

    private void runGc() {
        for (int i = 0; i < 5; i++) {
            runtime.gc();
        }
    }

    public void testAll(int itemCnt, int removeFactor) {
        runGc();
        for (BaseRemoveManyPerformer rmp : rmps) {
            rmp.testMe(itemCnt, removeFactor);
        }
        runGc();
        System.out.println("\n--------------------------\n");
    }

    public static class NaiveRemoveManyPerformer
            extends BaseRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            if (items.size() > 300000 && items instanceof ArrayList) {
                info("this removeItems is too slow, returning without processing");
                return items;
            }
            int i = 0;
            Iterator<Integer> iter = items.iterator();
            while (iter.hasNext()) {
                Integer item = iter.next();
                if (mustRemoveItem(item, i, removeFactor)) {
                    iter.remove();
                }
                i++;
            }
            return items;
        }

        @Override
        public List<Integer> createInitialList() {
            return new ArrayList<Integer>();
        }
    }

    public static class BetterNaiveRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
//            if (items.size() > 300000 && items instanceof ArrayList) {
//                info("this removeItems is too slow, returning without processing");
//                return items;
//            }

            for (int i = items.size(); --i >= 0;) {
                Integer item = items.get(i);
                if (mustRemoveItem(item, i, removeFactor)) {
                    items.remove(i);
                }
            }
            return items;
        }
    }

    public static class LinkedRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> createInitialList() {
            return new LinkedList<Integer>();
        }
    }

    public static class CreateNewRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            List<Integer> res = createResultList(items, removeFactor);
            int i = 0;

            for (Integer item : items) {
                if (mustRemoveItem(item, i, removeFactor)) {
                    // no-op
                } else {
                    res.add(item);
                }
                i++;
            }

            return res;
        }

        protected List<Integer> createResultList(List<Integer> items, int removeFactor) {
            return new ArrayList<Integer>();
        }
    }

    public static class SmartCreateNewRemoveManyPerformer
            extends CreateNewRemoveManyPerformer {

        @Override
        protected List<Integer> createResultList(List<Integer> items, int removeFactor) {
            int newCapacity = removeFactor == 0 ? items.size()
                    : (int) (items.size() * (removeFactor - 1L) / removeFactor + 1);
            //System.out.println("newCapacity=" + newCapacity);
            return new ArrayList<Integer>(newCapacity);
        }
    }

    public static class FasterSmartCreateNewRemoveManyPerformer
            extends SmartCreateNewRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            List<Integer> res = createResultList(items, removeFactor);

            for (int i = 0; i < items.size(); i++) {
                Integer item = items.get(i);
                if (mustRemoveItem(item, i, removeFactor)) {
                    // no-op
                } else {
                    res.add(item);
                }
            }

            return res;
        }
    }

    public static class ForwardInPlaceRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            int j = 0; // destination idx
            for (int i = 0; i < items.size(); i++) {
                Integer item = items.get(i);
                if (mustRemoveItem(item, i, removeFactor)) {
                    // no-op
                } else {
                    if (j < i) {
                        items.set(j, item);
                    }
                    j++;
                }
            }

            return items.subList(0, j);
        }
    }

    public static class MagicRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            for (int i = 0; i < items.size(); i++) {
                if (mustRemoveItem(items.get(i), i, removeFactor)) {
                    Integer retainedItem = removeSomeFromEnd(items, removeFactor, i);
                    if (retainedItem == null) {
                        items.remove(i);
                        break;
                    }
                    items.set(i, retainedItem);
                }
            }

            return items;
        }

        private Integer removeSomeFromEnd(List<Integer> items, int removeFactor, int lowerBound) {
            for (int i = items.size(); --i > lowerBound;) {
                Integer item = items.get(i);
                items.remove(i);
                if (!mustRemoveItem(item, i, removeFactor)) {
                    return item;
                }
            }
            return null;
        }
    }

    public static class GuavaArrayListRemoveManyPerformer
            extends BaseRemoveManyPerformer {

        @Override
        protected List<Integer> removeItems(List<Integer> items, final int removeFactor) {
            Iterables.removeIf(items, new Predicate<Integer>() {

                public boolean apply(Integer input) {
                    return mustRemoveItem(input, input, removeFactor);
                }
            });

            return items;
        }

        @Override
        protected List<Integer> createInitialList() {
            return new ArrayList<Integer>();
        }
    }

    public void testForOneItemCnt(int itemCnt) {
        testAll(itemCnt, 0);
        testAll(itemCnt, itemCnt);
        testAll(itemCnt, itemCnt - 1);
        testAll(itemCnt, 3);
        testAll(itemCnt, 2);
        testAll(itemCnt, 1);
    }

    public static void main(String[] args) {
        RemoveManyFromList t = new RemoveManyFromList();
        t.addPerformer(new NaiveRemoveManyPerformer());
        t.addPerformer(new BetterNaiveRemoveManyPerformer());
        t.addPerformer(new LinkedRemoveManyPerformer());
        t.addPerformer(new CreateNewRemoveManyPerformer());
        t.addPerformer(new SmartCreateNewRemoveManyPerformer());
        t.addPerformer(new FasterSmartCreateNewRemoveManyPerformer());
        t.addPerformer(new MagicRemoveManyPerformer());
        t.addPerformer(new ForwardInPlaceRemoveManyPerformer());
        t.addPerformer(new GuavaArrayListRemoveManyPerformer());

        t.testForOneItemCnt(1000);
        t.testForOneItemCnt(10000);
        t.testForOneItemCnt(100000);
        t.testForOneItemCnt(200000);
        t.testForOneItemCnt(300000);
        t.testForOneItemCnt(500000);
        t.testForOneItemCnt(1000000);
        t.testForOneItemCnt(10000000);
    }
}
37
WildWezyr

Comme d’autres l’ont dit, vous devriez commencer par établir une deuxième liste.

Toutefois, si vous souhaitez également modifier la liste sur place, la méthode la plus efficace consiste à utiliser Iterables.removeIf() de Guava. Si son argument est une liste, il fusionne les éléments retenus vers l'avant puis coupe simplement la fin - beaucoup plus rapidement que de supprimer () des éléments intérieurs un à un.

11
Kevin Bourrillion

Supprimer beaucoup d'éléments d'une ArrayList est une opération O(n^2). Je recommanderais simplement d'utiliser une variable LinkedList qui soit plus optimisée pour l'insertion et la suppression (mais pas pour l'accès aléatoire). LinkedList a un peu de surcharge de mémoire.

Si vous devez conserver ArrayList, vous feriez mieux de créer une nouvelle liste.

Mise à jour: Comparaison avec la création d'une nouvelle liste:

En réutilisant la même liste, le coût principal provient de la suppression du nœud et de la mise à jour des pointeurs appropriés dans LinkedList. C'est une opération constante pour n'importe quel nœud.

Lors de la construction d'une nouvelle liste, le coût principal provient de la création de la liste et de l'initialisation des entrées du tableau. Les deux sont des opérations bon marché. Vous pourriez également supporter le coût du redimensionnement du nouveau tableau principal de liste; en supposant que le tableau final est plus grand que la moitié du tableau entrant.

Donc, si vous supprimez un seul élément, l'approche LinkedList est probablement plus rapide. Si vous deviez supprimer tous les nœuds sauf un, la nouvelle approche par liste est probablement plus rapide.

Il y a plus de complications lorsque vous utilisez la gestion de la mémoire et la GC. J'aimerais les laisser de côté.

La meilleure option consiste à implémenter les alternatives vous-même et à analyser les résultats lors de l'exécution de votre charge typique.

6
notnoop

Je créerais une nouvelle variable List à laquelle ajouter les éléments, car la suppression d'un élément du milieu d'une liste coûte assez cher.

public static List<T> removeMany(List<T> items) {
    List<T> tempList = new ArrayList<T>(items.size()/2); //if about half the elements are going to be removed
    Iterator<T> iter = items.iterator();
    while (item : items) {
        // <cond> goes here
        if (/*<cond>: */i % 2 != 0) {
            tempList.add(item);
        }
    }
    return tempList;
}

EDIT: Je n’ai pas testé cela, donc il peut très bien y avoir de petites erreurs de syntaxe.

Deuxième EDIT: Utiliser une LinkedList est préférable si vous n'avez pas besoin d'un accès aléatoire, mais ajoutez des temps rapides. 

MAIS ...

Le facteur constant de ArrayList est inférieur à celui de LinkedList ( Ref ). Puisque vous pouvez raisonnablement deviner le nombre d'éléments qui seront supprimés (vous avez dit "environ la moitié" dans votre question), ajouter un élément à la fin d'une ArrayList est O(1) aussi longtemps que vous le souhaitez. ne pas avoir à le réaffecter. Donc, si vous pouvez faites une supposition raisonnable, je m'attendrais à ce que ArrayList soit légèrement plus rapide que LinkedList dans la plupart des cas. (Ceci s'applique au code que j'ai posté. Dans votre implémentation naïve, je pense que LinkedList sera plus rapide).

5
Chinmay Kanchi

Je suis désolé, mais toutes ces réponses sont sans intérêt, je pense: vous n’avez probablement pas à et ne devriez probablement pas utiliser une liste.

Si ce type de "requête" est courant, pourquoi ne pas créer une structure de données ordonnée qui élimine la nécessité de traverser tous les nœuds de données? Vous ne nous en parlez pas assez sur le problème, mais étant donné l'exemple que vous donnez, un simple arbre pourrait faire l'affaire. Il existe une surcharge d'insertion par élément, mais vous pouvez très rapidement trouver le sous-arbre contenant les nœuds correspondants, ce qui vous évite la plupart des comparaisons que vous effectuez maintenant. 

En outre:

  • En fonction du problème exact et de la structure de données que vous avez configurée, vous pouvez accélérer la suppression. Si les noeuds que vous souhaitez supprimer se réduisent à un sous-arbre ou à un élément similaire, vous devez simplement déposer ce sous-arbre, plutôt que de mettre à jour toute une série de nœuds de liste. 

  • Chaque fois que vous supprimez un élément de la liste, vous mettez à jour des pointeurs - par exemple, lastNode.next et nextNode.prev ou quelque chose - mais s'il s'avère que vous souhaitez également supprimer le nextNode , la mise à jour du pointeur que vous venez de provoquer est supprimée par une nouvelle mise à jour.)

2
fullreset

J'imagine que la création d'une nouvelle liste plutôt que la modification de la liste existante serait plus performante, en particulier lorsque le nombre d'éléments est aussi important que vous l'indiquez. Cela suppose que votre liste est une ArrayList, pas une LinkedList. Pour une LinkedList non circulaire, l'insertion est O (n), mais la suppression à une position d'itérateur existante est O (1); Dans ce cas, votre algorithme naïf devrait être suffisamment performant.

Sauf si la liste est une LinkedList, le coût du déplacement de la liste chaque fois que vous appelez remove() est probablement l'une des parties les plus coûteuses de la mise en œuvre. Pour les listes de tableaux, je considérerais d'utiliser:

public static <T> List<T> removeMany(List<T> items) {
    List<T> newList = new ArrayList<T>(items.size());
    Iterator<T> iter = items.iterator();
    while (iter.hasNext()) {
        T item = iter.next();
        // <cond> goes here
        if (/*<cond>: */i++ % 2 != 0) {
            newList.add(item);
        }
    }
    return newList;
}
2
LBushkin

Since speed is the most important metric, there's the possibility of using more memory and doing less recreation of lists (as mentioned in my comment). Actual performance impact would be fully dependent on how the functionality is used, though.

The algorithm assumes that at least one of the following is true:

  • all elements of the original list do not need to be tested. This could happen if we're really looking for the first N elements that match our condition, rather than all elements that match our condition.
  • it's more expensive to copy the list into new memory. This could happen if the original list uses more than 50% of allocated memory, so working in-place could be better or if memory operations turn out to be slower (that would be an unexpected result).
  • the speed penalty of removing elements from the list is too large to accept all at once, but spreading that penalty across multiple operations is acceptable, even if the overall penalty is larger than taking it all at once. This is like taking out a $200K mortgage: paying $1000 per month for 30 years is affordable on a monthly basis and has the benefits of owning a home and equity, even though the overall payment is 360K over the life of the loan.

Disclaimer: There's prolly syntax errors - I didn't try compiling anything.

First, subclass the ArrayList

public class ConditionalArrayList extends ArrayList {

  public Iterator iterator(Condition condition)
  { 
    return listIterator(condition);
  }

  public ListIterator listIterator(Condition condition)
  {
    return new ConditionalArrayListIterator(this.iterator(),condition); 
  }

  public ListIterator listIterator(){ return iterator(); }
  public iterator(){ 
    throw new InvalidArgumentException("You must specify a condition for the iterator"); 
  }
}

Then we need the helper classes:

public class ConditionalArrayListIterator implements ListIterator
{
  private ListIterator listIterator;
  Condition condition;

  // the two following flags are used as a quick optimization so that 
  // we don't repeat tests on known-good elements unnecessarially.
  boolean nextKnownGood = false;
  boolean prevKnownGood = false;

  public ConditionalArrayListIterator(ListIterator listIterator, Condition condition)
  {
    this.listIterator = listIterator;
    this.condition = condition;
  }

  public void add(Object o){ listIterator.add(o); }

  /**
   * Note that this it is extremely inefficient to 
   * call hasNext() and hasPrev() alternatively when
   * there's a bunch of non-matching elements between
   * two matching elements.
   */
  public boolean hasNext()
  { 
     if( nextKnownGood ) return true;

     /* find the next object in the list that 
      * matches our condition, if any.
      */
     while( ! listIterator.hasNext() )
     {
       Object next = listIterator.next();
       if( condition.matches(next) ) {
         listIterator.set(next);
         nextKnownGood = true;
         return true;
       }
     }

     nextKnownGood = false;
     // no matching element was found.
     return false;
  }

  /**
   *  See hasPrevious for efficiency notes.
   *  Copy & paste of hasNext().
   */
  public boolean hasPrevious()
  { 
     if( prevKnownGood ) return true;

     /* find the next object in the list that 
      * matches our condition, if any.
      */
     while( ! listIterator.hasPrevious() )
     {
       Object prev = listIterator.next();
       if( condition.matches(prev) ) {
         prevKnownGood = true;
         listIterator.set(prev);
         return true;
       }
     }

     // no matching element was found.
     prevKnwonGood = false;
     return false;
  }

  /** see hasNext() for efficiency note **/
  public Object next()
  {
     if( nextKnownGood || hasNext() ) 
     { 
       prevKnownGood = nextKnownGood;
       nextKnownGood = false;
       return listIterator.next();
     }

     throw NoSuchElementException("No more matching elements");
  }

  /** see hasNext() for efficiency note; copy & paste of next() **/
  public Object previous()
  {
     if( prevKnownGood || hasPrevious() ) 
     { 
       nextKnownGood = prevKnownGood;
       prevKnownGood = false;
       return listIterator.previous();                        
     }
     throw NoSuchElementException("No more matching elements");
  }

  /** 
   * Note that nextIndex() and previousIndex() return the array index
   * of the value, not the number of results that this class has returned.
   * if this isn't good for you, just maintain your own current index and
   * increment or decriment in next() and previous()
   */
  public int nextIndex(){ return listIterator.previousIndex(); }
  public int previousIndex(){ return listIterator.previousIndex(); }

  public remove(){ listIterator.remove(); }
  public set(Object o) { listIterator.set(o); }
}

and, of course, we need the condition interface:

/** much like a comparator... **/
public interface Condition
{
  public boolean matches(Object obj);
}

And a condition with which to test

public class IsEvenCondition {
{
  public boolean matches(Object obj){ return (Number(obj)).intValue() % 2 == 0;
}

and we're finally ready for some test code


    Condition condition = new IsEvenCondition();

    System.out.println("preparing items");
    startMillis = System.currentTimeMillis();
    List<Integer> items = new ArrayList<Integer>(); // Integer is for demo
    for (int i = 0; i < 1000000; i++) {
        items.add(i * 3); // just for demo
    }
    endMillis = System.currentTimeMillis();
    System.out.println("It took " + (endmillis-startmillis) + " to prepare the list. ");

    System.out.println("deleting items");
    startMillis = System.currentTimeMillis();
    // we don't actually ever remove from this list, so 
    // removeMany is effectively "instantaneous"
    // items = removeMany(items);
    endMillis = System.currentTimeMillis();
    System.out.println("after remove: items.size=" + items.size() + 
            " and it took " + (endMillis - startMillis) + " milli(s)");
    System.out.println("--> NOTE: Nothing is actually removed.  This algorithm uses extra"
                       + " memory to avoid modifying or duplicating the original list.");

    System.out.println("About to iterate through the list");
    startMillis = System.currentTimeMillis();
    int count = iterate(items, condition);
    endMillis = System.currentTimeMillis();
    System.out.println("after iteration: items.size=" + items.size() + 
            " count=" + count + " and it took " + (endMillis - startMillis) + " milli(s)");
    System.out.println("--> NOTE: this should be somewhat inefficient."
                       + " mostly due to overhead of multiple classes."
                       + " This algorithm is designed (hoped) to be faster than "
                       + " an algorithm where all elements of the list are used.");

    System.out.println("About to iterate through the list");
    startMillis = System.currentTimeMillis();
    int total = addFirst(30, items, condition);
    endMillis = System.currentTimeMillis();
    System.out.println("after totalling first 30 elements: total=" + total + 
            " and it took " + (endMillis - startMillis) + " milli(s)");

...

private int iterate(List<Integer> items, Condition condition)
{
  // the i++ and return value are really to prevent JVM optimization
  // - just to be safe.
  Iterator iter = items.listIterator(condition);
  for( int i=0; iter.hasNext()); i++){ iter.next(); }
  return i;
}

private int addFirst(int n, List<Integer> items, Condition condition)
{
  int total = 0;
  Iterator iter = items.listIterator(condition);
  for(int i=0; i<n;i++)
  {
    total += ((Integer)iter.next()).intValue();
  }
}

1
atk

Utilisez Collections Apache Commons . Plus précisément cette fonction . Ceci est implémenté essentiellement de la même manière que les gens vous suggèrent de l’implémenter (c’est-à-dire créer une nouvelle liste puis l’ajouter).

1
Jherico

Une chose que vous pouvez essayer est d'utiliser une variable LinkedList au lieu d'une ArrayList, comme avec une variable ArrayList, tous les autres éléments doivent être copiés si des éléments sont supprimés de la liste.

1
Fabian Steeg

Plutôt que de brouiller ma première réponse, qui est déjà assez longue, voici une deuxième option connexe: vous pouvez créer votre propre liste de tableaux et marquer les éléments comme "supprimés". Cet algorithme repose sur les hypothèses suivantes:

  • il est préférable de perdre du temps (vitesse plus faible) pendant la construction que de faire de même lors de l'opération d'enlèvement. En d'autres termes, cela déplace la pénalité de vitesse d'un endroit à un autre. 
  • il vaut mieux perdre de la mémoire maintenant, et le temps de ramasser les ordures une fois le résultat obtenu plutôt que de passer du temps à l’avant (vous êtes toujours coincé avec le ramassage des ordures ...).
  • une fois que la suppression a commencé, les éléments ne seront jamais ajoutés à la liste (sinon, il y a des problèmes de réaffectation de l'objet flags)

En outre, ceci n'est, encore une fois, pas testé et il y a donc des erreurs de syntaxe prlolly.

 public class FlaggedList étend ArrayList {
 vecteur privé <Boolean> flags = new ArrayList (); 
 private statique final String IN = Boolean.TRUE; // pas supprimé 
 private statique final String OUT = Boolean.FALSE; // enlevé 
 privé int enlevé = 0; 
 
 public MyArrayList () {this (1000000); } 
 public MyArrayList (estimation int) {
 super (estimation); 
 flags = new ArrayList (estimation); 
} 
 
 public void remove (int idx) {
 drapeaus.set (idx, OUT); 
 supprimé ++; 
} 
 
 public boolean isRemoved (int idx) {return flags.get (idx); } 
} 

et l'itérateur - il faudra peut-être plus de travail pour le garder synchronisé, et de nombreuses méthodes sont laissées de côté, cette fois:

 public class FlaggedListIterator implémente ListIterator 
 {
 int idx = 0; 
 
 public liste FlaggedList; 
 public FlaggedListIterator (liste FlaggedList ) 
 {
 this.list = list; 
} 
 public boolean hasNext () {
 while (idx <list.size () && list.isRemoved (idx ++)); 
 renvoie idx <list.size (); 
} 
} 
0
atk

Peut-être qu'une liste n'est pas la structure de données optimale pour vous? Pouvez-vous le changer? Peut-être pouvez-vous utiliser une arborescence dans laquelle les éléments sont triés de manière à ce que la suppression d'un nœud supprime tous les éléments répondant à la condition? Ou au moins accélérer vos opérations?

Dans votre exemple simpliste utilisant deux listes (une avec les éléments où i% 2! = 0 est vraie et l'autre avec les éléments où i% 2! = 0 est fausse) pourrait bien servir. Mais ceci est bien sûr très dépendant du domaine.

0
Arne Deutsch