Подтвердить что ты не робот

Как эффективно (производительность) удалить многие элементы из списка на Java?

У меня есть довольно большой список названных элементов ( >= 1,000,000 элементов) и некоторое условие, обозначенное символом <cond> который выбирает элементы, подлежащие удалению, и <cond> верно для многих (возможно, половины) элементов в моем списке.

Моя цель - эффективно удалять элементы, выбранные с помощью <cond> и сохранить все остальные элементы, список источников может быть изменен, может быть создан новый список - лучший способ сделать это должен быть выбран с учетом производительности.

Вот мой тестовый код:

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

и наивная реализация:

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

Как вы можете видеть, я использовал индекс элемента modulo 2 == 0 как условие удаления (<cond> ) - только для демонстрации.

Какая может быть лучшая версия removeMany и почему эта лучшая версия на самом деле лучше?

4b9b3361

Ответ 1

ОК, это время для результатов испытаний предлагаемых подходов. Здесь какие подходы я тестировал (имя каждого подхода также является именем класса в моих источниках):

  • NaiveRemoveManyPerformer - ArrayList с итератором и удалением - первая и наивная реализация, приведенная в моем вопросе.
  • BetterNaiveRemoveManyPerformer - ArrayList с обратной итерацией и удалением с конца на передний план.
  • LinkedRemoveManyPerformer - наивный итератор и удаляем, но работаем над LinkedList. Disadventage: работает только для LinkedList.
  • CreateNewRemoveManyPerformer - ArrayList делается как копия (добавляются только сохраненные элементы), итератор используется для перемещения входа ArrayList.
  • SmartCreateNewRemoveManyPerformer - лучше CreateNewRemoveManyPerformer - начальный размер (емкость) результата ArrayList установлен в конечный размер списка. Недостаток: вы должны знать конечный размер списка при запуске.
  • FasterSmartCreateNewRemoveManyPerformer - еще лучше (?) SmartCreateNewRemoveManyPerformer - используйте индекс позиции (items.get(idx)) вместо итератора.
  • MagicRemoveManyPerformer - работает на месте (без копирования списка) для ArrayList и сжимает отверстия (удаленные элементы) с начала с элементов из конца списка. Disadventage: этот подход изменяет порядок элементов в списке.
  • ForwardInPlaceRemoveManyPerformer - работает на месте ArrayList - перемещает удерживающие элементы для заполнения дыр, наконец возвращается сублист (окончательное удаление или очистка).
  • GuavaArrayListRemoveManyPerformer - Google Guava Iterables.removeIf используется для ArrayList - почти то же самое, что и ForwardInPlaceRemoveManyPerformer, но делает окончательное удаление элементов в конце списка.

Полный исходный код приведен в конце этого ответа.

Тесты, выполняемые с разными размерами списка (от 10 000 элементов до 10 000 000 элементов) и различные факторы удаления (указать, сколько элементов необходимо удалить из списка).

Как я написал здесь в комментариях для других ответов - я думал, что копирование элементов из ArrayList во второй ArrayList будет быстрее, чем повторение LinkedList и просто удаление элементов. В Sun Java Documentation говорится, что постоянный коэффициент ArrayList является низким по сравнению с реализацией LinkedList, но на удивление это не так в моей проблеме.

На практике LinkedList с простой итерацией и удалением имеет наибольшую производительность в большинстве случаев (этот подход реализован в LinkedRemoveManyPerformer). Обычно производительность MagicRemoveManyPerformer сравнима с LinkedRemoveManyPerformer, другие подходы значительно медленнее. Google Guava GuavaArrayListRemoveManyPerformer работает медленнее, чем ручной код (потому что мой код не удаляет ненужные элементы в конце списка).

Примеры результатов для удаления 500 000 элементов из 1 000 000 исходных элементов:

  • NaiveRemoveManyPerformer: тест не выполнен - ​​я не тот пациент, но он хуже, чем BetterNaiveRemoveManyPerformer.
  • BetterNaiveRemoveManyPerformer: 226080 милли (ы)
  • LinkedRemoveManyPerformer: 69 милли (с)
  • CreateNewRemoveManyPerformer: 246 милли (ы)
  • SmartCreateNewRemoveManyPerformer: 112 милли (с)
  • FasterSmartCreateNewRemoveManyPerformer: 202 милли (с)
  • MagicRemoveManyPerformer: 74 милли (ы)
  • ForwardInPlaceRemoveManyPerformer: 69 милли (с)
  • GuavaArrayListRemoveManyPerformer: 118 милли (ы)

Примеры результатов для удаления 1 предмета из 1,000,000 исходных элементов (первый элемент удален):

  • BetterNaiveRemoveManyPerformer: 34 милли (ы)
  • LinkedRemoveManyPerformer: 41 милли (ы)
  • CreateNewRemoveManyPerformer: 253 милли (ы)
  • SmartCreateNewRemoveManyPerformer: 108 милли (ы)
  • FasterSmartCreateNewRemoveManyPerformer: 71 милли (ы)
  • MagicRemoveManyPerformer: 43 милли (ы)
  • ForwardInPlaceRemoveManyPerformer: 73 милли (ы)
  • GuavaArrayListRemoveManyPerformer: 78 милли (ы)

Примеры результатов для удаления 333 334 предметов из 1,000,000 исходных элементов:

  • BetterNaiveRemoveManyPerformer: 253206 милли (ы)
  • LinkedRemoveManyPerformer: 69 милли (ы)
  • CreateNewRemoveManyPerformer: 245 милли (ы)
  • SmartCreateNewRemoveManyPerformer: 111 милли (ы)
  • FasterSmartCreateNewRemoveManyPerformer: 203 милли (ы)
  • MagicRemoveManyPerformer: 69 милли (ы)
  • ForwardInPlaceRemoveManyPerformer: 72 милли (ы)
  • GuavaArrayListRemoveManyPerformer: 102 милли (ы)

Примеры результатов для удаления 1 000 000 (все) элементов из 1,000,000 исходных элементов (все элементы удаляются, но с обработкой один за другим, если вы знаете, что все элементы должны быть удалены, список должен быть просто удален):

  • BetterNaiveRemoveManyPerformer: 58 милли (ы)
  • LinkedRemoveManyPerformer: 88 милли (ы)
  • CreateNewRemoveManyPerformer: 95 милли (ы)
  • SmartCreateNewRemoveManyPerformer: 91 милли (ы)
  • FasterSmartCreateNewRemoveManyPerformer: 48 милли (ы)
  • MagicRemoveManyPerformer: 61 милли (ы)
  • ForwardInPlaceRemoveManyPerformer: 49 милли (ы)
  • GuavaArrayListRemoveManyPerformer: 133 милли (ы)

Мои окончательные выводы: используйте гибридный подход - если вы имеете дело с LinkedList - простая итерация и удаление лучше всего, если вы имеете дело с ArrayList - это зависит, если порядок элементов важен - используйте ForwardInPlaceRemoveManyPerformer, тогда, если порядок вещей может быть изменен - ​​лучший выбор MagicRemoveManyPerformer. Если атрибут удаления известен априори (вы знаете, сколько элементов будет удалено или сохранено), тогда может быть предложено несколько дополнительных условий для выбора подхода, выполняющего еще лучше в конкретной ситуации. Но известный фактор удаления - это не обычный случай... Google Guava Iterables.removeIf - такое гибридное решение, но с немного отличающимся предположением (исходный список должен быть изменен, новый нельзя создать и порядок вещей всегда имеет значение) - это наиболее распространенные предположения поэтому removeIf - лучший выбор в большинстве случаев в реальной жизни.

Заметьте также, что все хорошие подходы (наивные - это не очень хорошо!) достаточно хороши - любой из них соскальзывал, просто отлично в реальном приложении, но следует избегать наивного подхода.

Наконец - мой исходный код для тестирования.

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

Ответ 2

Как говорили другие, ваша первая наклонность должна состоять в том, чтобы просто создать второй список.

Но, если вы хотите также попробовать изменить список на месте, эффективный способ сделать это - использовать Iterables.removeIf() из Гуавы. Если его аргумент представляет собой список, он объединяет удерживаемые элементы по направлению к фронту, а затем просто отрубает конец - намного быстрее, чем удаляет() внутренние элементы один за другим.

Ответ 3

Удаление множества элементов из ArrayList - это операция O(n^2). Я бы рекомендовал просто использовать LinkedList, который более оптимизирован для вставки и удаления (но не для произвольного доступа). LinkedList имеет немного накладных расходов памяти.

Если вам нужно сохранить ArrayList, вам лучше создать новый список.

Обновление: сравнение с созданием нового списка:

Повторное использование того же списка, основная стоимость исходит от удаления node и обновления соответствующих указателей в LinkedList. Это постоянная операция для любого node.

При создании нового списка основная стоимость исходит от создания списка и инициализации записей массива. Оба являются дешевыми операциями. Вы можете увеличить стоимость изменения размера массива нового списка; предполагая, что последний массив больше половины входящего массива.

Итак, если вы должны удалить только один элемент, то подход LinkedList, вероятно, будет быстрее. Если вы должны удалить все узлы, кроме одного, возможно, новый подход к списку быстрее.

У вас больше осложнений при управлении памятью и GC. Я хотел бы оставить их.

Лучший вариант - реализовать сами альтернативы и сравнить результаты при выполнении типичной нагрузки.

Ответ 4

Я бы сделал новый List, чтобы добавить элементы в, так как удаление элемента из середины списка довольно дорого.

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: я не тестировал это, поэтому вполне могут быть небольшие синтаксические ошибки.

Второй EDIT: использование LinkedList лучше, когда вам не нужен произвольный доступ, но быстрый добавочный.

НО...

Постоянный коэффициент для ArrayList меньше, чем для LinkedList (Ref). Поскольку вы можете разумно предположить, сколько элементов будет удалено (вы сказали "примерно половина" в своем вопросе), добавление элемента в конец ArrayList равно O (1), если у вас нет перераспределить его. Итак, если вы можете сделать разумное предположение, я ожидал бы, что ArrayList будет в несколько раз быстрее, чем LinkedList. (Это относится к коду, который я опубликовал. В вашей наивной реализации я думаю, что LinkedList будет быстрее).

Ответ 5

Я бы предположил, что создание нового списка, а не изменение существующего списка, будет более результативным - особенно когда количество элементов будет таким же большим, как вы указываете. Предполагается, что ваш список ArrayList, а не LinkedList. Для некруглой LinkedList вставка - O (n), но удаление в существующей позиции итератора равно O (1); в этом случае ваш наивный алгоритм должен быть достаточно эффективным.

Если список не является LinkedList, стоимость сдвига списка каждый раз, когда вы вызываете remove(), вероятно, является одной из самых дорогих частей реализации. Для списков массивов я бы рассмотрел использование:

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

Ответ 6

Извините, но все эти ответы не имеют смысла, я думаю: вам, вероятно, не нужно и, вероятно, не следует использовать список.

Если такой тип запроса является общим, почему бы не построить упорядоченную структуру данных, которая устраняет необходимость прохождения всех узлов данных? Вы не говорите нам достаточно о проблеме, но, учитывая пример, который вы предоставляете, простое дерево может сделать трюк. Там накладные расходы на ввод, но вы можете очень быстро найти поддерево, содержащее узлы, которые соответствуют, и поэтому вы избегаете большинства сравнений, которые вы сейчас делаете.

Далее

  • В зависимости от точной проблемы и точной структуры данных вы можете ускорить удаление - если узлы, которые вы хотите убить, сводят к поддереву или что-то в этом роде, вы просто отбрасываете это поддерево, вместо того, чтобы обновлять множество узлов списка.

  • Каждый раз, когда вы удаляете элемент списка, вы обновляете указатели - например, lastNode.next и nextNode.prev или что-то в этом роде, но если оказывается, что вы также хотите удалить nextNode, тогда указатель обновит вас только что вызванное будет выброшено новым обновлением.)

Ответ 7

Одна вещь, которую вы могли бы попробовать, - использовать LinkedList вместо ArrayList, как и в случае с ArrayList, все остальные элементы необходимо скопировать, если элементы удалены из списка.

Ответ 8

Используйте Коллекции сообщества Apache. В частности эта функция. Это реализовано, по сути, так же, как люди предполагают, что вы его реализуете (т.е. Создаете новый список, а затем добавляете его).

Ответ 9

Поскольку скорость является самой важной метрикой, есть возможность использовать больше памяти и делать меньше отдыха списков (как упоминалось в моем комментарии). Фактическое влияние на производительность будет в полной мере зависеть от того, как эти функции используются.

В алгоритме предполагается, что выполняется хотя бы одно из следующих утверждений:

  • все элементы исходного списка не нуждаются в проверке. Это может произойти, если мы действительно ищем первые N элементов, которые соответствуют нашему условию, а не все элементы, которые соответствуют нашему условию.
  • Более дорогое копирование списка в новую память. Это может произойти, если исходный список использует более 50% выделенной памяти, поэтому работа на месте может быть лучше или если операции с памятью оказываются более медленными (это было бы неожиданным результатом).
  • ограничение скорости удаления элементов из списка слишком велико, чтобы принимать все одновременно, но распространение этого штрафа на несколько операций приемлемо, даже если общее наказание больше, чем забрать его все сразу. Это похоже на получение ипотеки за 200 000 долларов США: выплата 1000 долларов США в месяц в течение 30 лет является доступной на ежемесячной основе и имеет преимущества владения домом и капиталом, хотя общий платеж составляет 360 тысяч долларов за весь срок действия займа.

Отказ от ответственности: имеются сильные синтаксические ошибки - я не пытался компилировать что-либо.

Сначала подкласс 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"); 
  }
}

Тогда нам нужны вспомогательные классы:

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

и, конечно, нам нужен интерфейс условий:

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

И условие, с помощью которого можно протестировать

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

и мы, наконец, готовы к некоторому тестовому коду

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

Ответ 10

Возможно, список не является оптимальной структурой данных для вас? Можете ли вы его изменить? Возможно, вы можете использовать дерево, в котором элементы сортируются таким образом, что удаление одного node удаляет все элементы, соответствующие этому условию? Или, по крайней мере, ускорить ваши операции?

В вашем упрощенном примере, используя два списка (один с элементами, где i% 2!= 0 является истинным, а один с элементами, где i% 2!= 0 - false), может хорошо служить. Но это, конечно, очень зависит от домена.

Ответ 11

Вместо того, чтобы мутировать мой первый ответ, который уже довольно длинный, вот второй, связанный с ним вариант: вы можете создать свой собственный ArrayList и обозначить вещи как "удаленные". Этот алгоритм делает предположения:

  • Лучше тратить время (более низкая скорость) во время строительства, чем делать то же самое во время операции удаления. Другими словами, он перемещает ограничение скорости от одного места к другому.
  • Лучше потерять память сейчас, а время сбора мусора после того, как результат будет вычисляться, а не тратить время на фронт (вы всегда застряли со сбором мусора...).
  • После начала удаления элементы никогда не будут добавлены в список (в противном случае возникают проблемы с перераспределением объекта flags)

Кроме того, это еще раз не проверено, поэтому есть ошибки prlolly синтаксиса.

public class FlaggedList extends ArrayList {
  private Vector<Boolean> flags = new ArrayList();
  private static final String IN = Boolean.TRUE;  // not removed
  private static final String OUT = Boolean.FALSE; // removed
  private int removed = 0;

  public MyArrayList(){ this(1000000); }
  public MyArrayList(int estimate){
    super(estimate);
    flags = new ArrayList(estimate);
  }

  public void remove(int idx){
    flags.set(idx, OUT);
    removed++;
  }

  public boolean isRemoved(int idx){ return flags.get(idx); }
}

и итератор - может потребоваться больше работы, чтобы синхронизировать его, и многие методы не учитываются, на этот раз:

public class FlaggedListIterator implements ListIterator
{
  int idx = 0;

  public FlaggedList list;
  public FlaggedListIterator(FlaggedList list)
  {
    this.list = list;
  }
  public boolean hasNext() {
    while(idx<list.size() && list.isRemoved(idx++)) ;
    return idx < list.size();
  }
}

Ответ 12

Попробуйте реализовать рекурсию в свой алгоритм.