it-swarm.com.de

Wie kann man effizient (Leistung) viele Elemente aus der Liste in Java entfernen?

Ich habe eine ziemlich große Liste mit benannten Elementen (> = 1.000.000 Elemente) und eine Bedingung, die mit <cond> angegeben ist und die zu löschende Elemente auswählt, und <cond> gilt für viele (möglicherweise die Hälfte) der Elemente auf meiner Liste.

Mein Ziel ist es, die von <cond> ausgewählten Elemente effizient zu entfernen und alle anderen Elemente beizubehalten. Die Quellenliste kann geändert und eine neue Liste erstellt werden. Der beste Weg, dies zu tun, sollte unter Berücksichtigung der Leistung gewählt werden.

Hier ist mein Testcode:

    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)");

und naive Umsetzung:

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;
}

Wie Sie sehen, habe ich Item-Index Modulo 2 == 0 als Entfernungsbedingung (<cond>) verwendet - nur zu Demonstrationszwecken.

Welche bessere Version von removeMany kann zur Verfügung gestellt werden und warum ist diese bessere Version eigentlich besser?

29
WildWezyr

OK, es ist Zeit für Testergebnisse der vorgeschlagenen Ansätze. Welche Ansätze habe ich getestet (Name jedes Ansatzes ist auch Klassenname in meinen Quellen):

  1. NaiveRemoveManyPerformer- ArrayListmit Iterator und remove - erste und naive Implementierung in meiner Frage.
  2. BetterNaiveRemoveManyPerformer- ArrayListmit Rückwärts-Iteration und Entfernung von Ende zu Front.
  3. LinkedRemoveManyPerformer- naiver Iterator und Entferner, der jedoch mit LinkedListarbeitet. Disadventage: funktioniert nur für LinkedListname__.
  4. CreateNewRemoveManyPerformer- ArrayListwird als Kopie erstellt (es werden nur beibehaltene Elemente hinzugefügt), der Iterator wird zum Durchlaufen der Eingabe ArrayListverwendet.
  5. SmartCreateNewRemoveManyPerformer- besser CreateNewRemoveManyPerformer- Anfangsgröße (Kapazität) des Ergebnisses ArrayListwird auf die endgültige Listengröße gesetzt. Nachteil: Sie müssen die endgültige Größe der Liste beim Start kennen.
  6. FasterSmartCreateNewRemoveManyPerformer- noch besser (?) SmartCreateNewRemoveManyPerformer- Verwenden Sie den Elementindex (items.get(idx)) anstelle des Iterators.
  7. MagicRemoveManyPerformer- arbeitet an Ort und Stelle (keine Listenkopie) für ArrayListund komprimiert Löcher (entfernte Elemente) von Anfang an mit Elementen vom Ende der Liste. Enttäuschung: Dieser Ansatz ändert die Reihenfolge der Elemente in der Liste.
  8. ForwardInPlaceRemoveManyPerformer- funktioniert an Ort und Stelle für ArrayList- Bewegen Sie die verbleibenden Elemente, um die Löcher zu füllen. Schließlich wird die Teilliste zurückgegeben (endgültiges Entfernen oder Löschen).
  9. GuavaArrayListRemoveManyPerformer- Google Guava Iterables.removeIf wird für ArrayListverwendet - fast das gleiche wie ForwardInPlaceRemoveManyPerformername__, führt jedoch die endgültige Entfernung der Elemente am Ende der Liste aus.

Den vollständigen Quellcode finden Sie am Ende dieser Antwort.

Die Tests wurden mit unterschiedlichen Listengrößen (von 10.000 bis 10.000.000 Elementen) und verschiedenen Entfernungsfaktoren (Angabe, wie viele Elemente aus der Liste entfernt werden müssen) durchgeführt.

Wie ich hier in Kommentaren zu anderen Antworten gepostet habe - habe ich gedacht, dass das Kopieren von Elementen aus ArrayListin den zweiten ArrayListschneller ist als das Durchlaufen von LinkedListund das Entfernen von Elementen. Die Java-Dokumentation von Sun sagt, dass der konstante Faktor von ArrayListim Vergleich zu der für die LinkedListname__-Implementierung niedrig ist, aber überraschenderweise ist dies bei meinem Problem nicht der Fall.

In der Praxis hat LinkedListmit einfacher Iteration und Entfernung in den meisten Fällen die beste Leistung (dieser Ansatz wird in LinkedRemoveManyPerformerimplementiert). Normalerweise ist nur MagicRemoveManyPerformerdie Leistung vergleichbar mit LinkedRemoveManyPerformername__, andere Ansätze sind wesentlich langsamer. Google Guava GuavaArrayListRemoveManyPerformerist langsamer als von Hand erstellter ähnlicher Code (da mein Code keine unnötigen Elemente am Ende der Liste entfernt).

Beispielergebnisse zum Entfernen von 500.000 Elementen aus 1.000.000 Quellelementen:

  1. NaiveRemoveManyPerformername__: Test nicht durchgeführt - Ich bin nicht der Patient, aber er ist schlechter als BetterNaiveRemoveManyPerformername__.
  2. BetterNaiveRemoveManyPerformername__: 226080 milli (s)
  3. LinkedRemoveManyPerformername__: 69 milli (s)
  4. CreateNewRemoveManyPerformername__: 246 milli (s)
  5. SmartCreateNewRemoveManyPerformername__: 112 milli (s)
  6. FasterSmartCreateNewRemoveManyPerformername__: 202 milli (s)
  7. MagicRemoveManyPerformername__: 74 milli (s)
  8. ForwardInPlaceRemoveManyPerformername__: 69 milli (s)
  9. GuavaArrayListRemoveManyPerformername__: 118 milli (s)

Beispielergebnisse zum Entfernen eines Elements aus 1.000.000 Quellelementen (erstes Element wird entfernt):

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

Beispielergebnisse zum Entfernen von 333.334 Elementen aus 1.000.000 Quellelementen:

  1. BetterNaiveEntfernenManyPerformer: 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. GuaveArrayListeEntfernenManyPerformer: 102 milli (s)

Beispielergebnisse zum Entfernen von 1.000.000 (allen) Elementen aus 1.000.000 Quellelementen (alle Elemente werden entfernt, jedoch mit Einzelverarbeitung; wenn Sie a priori wissen, dass alle Elemente entfernt werden sollen, sollte die Liste einfach gelöscht werden):

  1. BetterNaiveEntfernenManyPerformer: 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. GuaveArrayListeEntfernenManyPerformer: 133 milli (s)

Meine abschließende Schlussfolgerung: Verwenden Sie einen hybriden Ansatz - wenn Sie mit LinkedList arbeiten - einfache Iteration und Entfernung ist am besten, wenn Sie mit ArrayList arbeiten - es hängt davon ab, ob die Reihenfolge der Elemente wichtig ist - verwenden Sie ForwardInPlaceRemoveManyPerformer. Wenn der Entfernungsfaktor a priori bekannt ist (Sie wissen, wie viele Elemente entfernt oder behalten werden), können einige weitere Bedingungen zur Auswahl des Ansatzes gestellt werden, der in bestimmten Situationen noch besser ist. Bekannter Entfernungsfaktor ist jedoch nicht üblich ... Google Guava Iterables.removeIf ist eine solche hybride Lösung, jedoch mit etwas anderer Annahme (ursprüngliche Liste muss geändert werden, neue Liste kann nicht erstellt werden und die Reihenfolge der Artikel ist immer von Bedeutung) - dies sind die häufigsten Annahmen, also removeIfist in den meisten Fällen die beste Wahl.

Beachten Sie auch, dass alle guten Ansätze (naiv ist nicht gut!) Gut genug sind - jeder von ihnen sollte in der realen Anwendung gut funktionieren, aber ein naiver Ansatz muss vermieden werden.

Endlich - mein Quellcode zum Testen.

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

Wie andere bereits gesagt haben, sollten Sie zunächst eine zweite Liste erstellen.

Wenn Sie jedoch auch versuchen möchten, die Liste direkt an Ort und Stelle zu bearbeiten, können Sie dies mit Hilfe von Iterables.removeIf() von Guava aus tun. Wenn das Argument eine Liste ist, vereinigt sie die zurückgehaltenen Elemente nach vorne und schneidet das Ende einfach ab - viel schneller als das Entfernen von inneren Elementen nacheinander.

11

Das Entfernen einer Vielzahl von Elementen aus einer ArrayList ist eine O(n^2)-Operation. Ich würde empfehlen, einfach eine LinkedList zu verwenden, die für das Einfügen und Entfernen optimiert ist (jedoch nicht für den Direktzugriff). LinkedList hat etwas Speicherplatz.

Wenn Sie ArrayList beibehalten müssen, können Sie besser eine neue Liste erstellen.

Update: Vergleich mit dem Erstellen einer neuen Liste:

Bei der Wiederverwendung derselben Liste werden die Hauptkosten dadurch verursacht, dass der Knoten gelöscht und die entsprechenden Zeiger in LinkedList aktualisiert werden. Dies ist eine konstante Operation für jeden Knoten.

Beim Erstellen einer neuen Liste entstehen die Hauptkosten aus dem Erstellen der Liste und dem Initialisieren von Array-Einträgen. Beide sind billige Operationen. Möglicherweise fallen auch Kosten für die Größenänderung des neuen Listen-Backend-Arrays an. Angenommen, das letzte Array ist größer als die Hälfte des eingehenden Arrays.

Wenn Sie also nur ein Element entfernen, ist der Ansatz von LinkedList wahrscheinlich schneller. Wenn Sie alle Knoten außer einem löschen, ist der neue Listenansatz wahrscheinlich schneller.

Es gibt weitere Komplikationen, wenn Sie Speicherverwaltung und GC mitbringen. Ich möchte diese auslassen.

Die beste Option ist, die Alternativen selbst zu implementieren und die Ergebnisse zu vergleichen, wenn Sie Ihre typische Last ausführen.

6
notnoop

Ich würde eine neue List erstellen, um die Elemente hinzuzufügen, da das Entfernen eines Elements aus der Mitte einer Liste ziemlich teuer ist.

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: Ich habe das nicht getestet, daher kann es zu kleinen Syntaxfehlern kommen.

Zweites BEARBEITEN: Die Verwendung einer LinkedList ist besser, wenn Sie keinen Direktzugriff benötigen, sondern schnelles Hinzufügen. 

ABER ...

Der konstante Faktor für ArrayList ist kleiner als der für LinkedList ( Ref ). Da Sie eine vernünftige Vorstellung davon machen können, wie viele Elemente entfernt werden sollen (Sie haben in Ihrer Frage "ungefähr die Hälfte" gesagt), ist das Hinzufügen eines Elements am Ende einer ArrayList O(1), solange Sie dies tun Sie müssen es nicht neu zuweisen. Wenn Sie also can eine vernünftige Annahme machen, würde ich davon ausgehen, dass die ArrayList in den meisten Fällen geringfügig schneller ist als die LinkedList. (Dies gilt für den Code, den ich gepostet habe. In Ihrer naiven Implementierung denke ich, dass LinkedList schneller sein wird).

5
Chinmay Kanchi

Es tut mir leid, aber bei all diesen Antworten fehlt der Punkt, denke ich: Sie müssen und müssen wahrscheinlich keine Liste verwenden.

Wenn diese Art von "Abfrage" üblich ist, können Sie eine geordnete Datenstruktur erstellen, die es unnötig macht, alle Datenknoten zu durchlaufen. Sie erzählen uns nicht genug über das Problem, aber angesichts des Beispiels, das Sie angeben, könnte ein einfacher Baum den Trick ausführen. Es gibt einen Einfüge-Overhead pro Element, aber Sie können sehr schnell den Teilbaum finden, der übereinstimmende Knoten enthält. Daher vermeiden Sie die meisten Vergleiche, die Sie jetzt durchführen. 

Außerdem:

  • Abhängig von dem genauen Problem und der genauen Datenstruktur, die Sie eingerichtet haben, können Sie das Löschen beschleunigen. Wenn die Knoten, die Sie abtöten möchten, auf einen Teilbaum oder etwas ähnliches reduziert werden, brauchen Sie nur drop that Teilbaum, anstatt eine ganze Reihe von Listenknoten zu aktualisieren. 

  • Jedes Mal, wenn Sie ein Listenelement entfernen, aktualisieren Sie Zeiger - z. B. lastNode.next und nextNode.prev oder etwas anderes. Wenn sich jedoch herausstellt, dass Sie das nextNode, dann wird die gerade von Ihnen verursachte Zeigeraktualisierung durch ein neues Update verworfen.)

2
fullreset

Ich würde mir vorstellen, dass das Erstellen einer neuen Liste, anstatt die vorhandene Liste zu ändern, leistungsfähiger wäre - insbesondere, wenn die Anzahl der Elemente so groß ist, wie Sie es angeben. Dies setzt voraus, dass Ihre Liste ein ArrayListist, nicht ein LinkedListname__. Für nicht kreisförmige LinkedListist das Einfügen O (n), aber das Entfernen an einer vorhandenen Iteratorposition ist O (1); In diesem Fall sollte Ihr naiver Algorithmus ausreichend leistungsfähig sein.

Wenn die Liste nicht LinkedListist, sind die Kosten für das Verschieben der Liste bei jedem Aufruf von remove() wahrscheinlich einer der teuersten Teile der Implementierung. Für Array-Listen würde ich Folgendes in Betracht ziehen:

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

Verwenden Sie Apache Commons Collections . Speziell diese Funktion . Dies wird im Wesentlichen auf die gleiche Weise implementiert, auf die die Benutzer vorschlagen, sie zu implementieren (d. H. Eine neue Liste erstellen und dann hinzufügen).

1
Jherico

Sie können versuchen, eine LinkedList anstelle einer ArrayList zu verwenden, da bei einer ArrayList alle anderen Elemente kopiert werden müssen, wenn Elemente aus der Liste entfernt werden.

1
Fabian Steeg

Anstatt meine erste Antwort, die bereits ziemlich lang ist, zu vermasseln, ist hier eine zweite, verwandte Option: Sie können Ihre eigene ArrayList erstellen und Dinge als "entfernt" kennzeichnen. Dieser Algorithmus macht die Annahmen:

  • es ist besser, Zeit (geringere Geschwindigkeit) während des Baus zu verschwenden, als dasselbe während des Entfernungsvorgangs zu tun. Mit anderen Worten, es verschiebt die Geschwindigkeitsstrafe von einem Ort zum anderen. 
  • es ist besser, jetzt Speicher zu verschwenden, und die Zeit, die das Sammeln von Müll nach der Berechnung des Ergebnisses benötigt, ist besser, als die Zeit im Vorfeld zu verbringen (Sie sind immer mit dem Sammeln von Zeit beschäftigt ...).
  • sobald die Entfernung beginnt, werden Elemente nie zur Liste hinzugefügt (andernfalls gibt es Probleme bei der Neuzuweisung des Flagsobjekts).

Auch dies wird erneut nicht getestet, so dass Prololly-Syntaxfehler auftreten.

 public class FlaggedList erweitert ArrayList {
 private Vector <Boolean> flags = new ArrayList (); 
 private static final Zeichenfolge IN = Boolean.TRUE; // nicht entfernt 
 private static final String OUT = Boolean.FALSE; // Entfernte 
 private int Entfernt = 0; 
 
 public MyArrayList () {this (1000000); } 
 public MyArrayList (int-Schätzung) {
 super (Schätzung); 
 flags = neue ArrayList (Schätzung); 
} 
 
 .] public void remove (int idx) {
 flags.set (idx, OUT); 
 gelöscht ++; 
} 
 
 public boolean isRemoved (int idx) {return flags.get (idx); } 
} 

und der Iterator - möglicherweise ist mehr Arbeit erforderlich, um die Synchronisierung aufrechtzuerhalten, und viele Methoden werden dieses Mal ausgelassen:

 public class FlaggedListIterator implementiert ListIterator 
 {
 int idx = 0; 
 
 public FlaggedList-Liste; 
 public FlaggedListIterator (FlaggedList-Liste.) ) 
 {
 this.list = list; 
} 
 public boolean hasNext () {
 while (idx <list.size () && list.isRemoved (idx ++)); 
 return idx <list.size (); 
} 
} 
0
atk

Vielleicht ist eine Liste nicht die optimale Datenstruktur für Sie? Kannst du es aendern? Vielleicht können Sie eine Baumstruktur verwenden, in der die Elemente so sortiert werden, dass beim Löschen eines Knotens alle Elemente entfernt werden, die die Bedingung erfüllen? Oder beschleunigen Sie zumindest Ihren Betrieb?

In Ihrem einfachen Beispiel, das zwei Listen verwendet (eine mit den Elementen, bei denen i% 2! = 0 wahr ist, und eine mit den Elementen, bei denen i% 2! = 0 falsch ist), könnte sich als nützlich erweisen. Dies ist jedoch natürlich sehr domänenabhängig.

0
Arne Deutsch