Лучший способ удалить один элемент arraylist из другого arraylist

Каков наилучший метод производительности в Java (7,8), чтобы исключить элементы integer одного Arraylist из другого. Все элементы уникальны в первом и втором списках.

В настоящий момент я знаю метод API removeall и использую его следующим образом:

tempList.removeAll(tempList2);

Проблема возникает, когда я работаю с arraylists, имеет более 10000 элементов. Например, когда я удаляю 65000 элементов, время задержки составляет около 2 секунд. Но мне нужно работать с более большими списками с более чем 1000000 элементами.

Какова стратегия для этой проблемы?

Возможно, что-то с новым Stream API должно решить?

Ответ 1

TL;DR:

Держите его простым. Используйте

list.removeAll(new HashSet<T>(listOfElementsToRemove));

вместо.


Как уже упоминал Эран в его ответе: низкая производительность связана с тем, что псевдокод общей реализации removeAll

public boolean removeAll(Collection<?> c) {
    for (each element e of this) {
        if (c.contains(e)) {
            this.remove(e);
        }
    }
}

Таким образом, вызов contains, который выполняется в списке удаляемых элементов, приведет к производительности O (n * k) (где n - количество удаляемых элементов, а k - количество элементы в списке, который вызван методом).

Наивно можно представить, что вызов this.remove(e) на List также может иметь O (k), и эта реализация также будет иметь квадратичную сложность. Но это не так: вы упомянули, что списки - это специально ArrayList экземпляры. И метод ArrayList#removeAll реализуется для делегирования на метод с именем batchRemove, который непосредственно работает с базовым массивом и не удаляет элементы по отдельности.

Итак, все, что вам нужно сделать, это убедиться, что поиск в коллекции, содержащей удаляемые элементы, является быстрым - предпочтительно O (1). Это может быть достигнуто путем помещения этих элементов в Set. В конце концов, его можно просто записать как

list.removeAll(new HashSet<T>(listOfElementsToRemove));

Боковые заметки:

Ответ от Eran имеет ИМХО два основных недостатка: Прежде всего, это требует сортировки списков, что является O (n * logn) - и это просто не нужно. Но что более важно (и очевидно): Сортировка, скорее всего, изменит порядок элементов! Что делать, если это просто не желательно?

Дистанционно связанный: Есть некоторые другие тонкости, участвующие в реализациях removeAll. Например, HashSet removeAll метод на удивление медленный в некоторых случаях. Хотя это также сводится к O (n * n), когда элементы, которые нужно удалить, сохраняются в списке, точное поведение может действительно удивить в данном конкретном случае.

Ответ 2

Ну, поскольку removeAll проверяет каждый элемент tempList, появляется ли он в tempList2, время работы пропорционально размеру первого списка, умноженному на размер второго списка, что означает O(N^2) если один из двух списков очень мал и может рассматриваться как "постоянный размер".

Если, с другой стороны, вы предварительно сортируете списки, а затем перебираете оба списка с помощью одной итерации (аналогично шагу слияния в сортировке слияния), сортировка займет O(NlogN) и итерация O(N), что дает вам общее время работы O(NlogN). Здесь N - размер большего из двух списков.

Если вы можете заменить списки на отсортированную структуру (возможно, TreeSet, поскольку вы сказали, что элементы уникальны), вы можете реализовать removeAll в линейном времени, так как вам не придется выполнять сортировку.

Я не тестировал его, но что-то вроде этого может работать (при условии, что отсортированы как tempList, так и tempList2):

Iterator<Integer> iter1 = tempList.iterator();
Iterator<Integer> iter2 = tempList2.iterator();
Integer current = null;
Integer current2 = null;
boolean advance = true;
while (iter1.hasNext() && iter2.hasNext()) {
    if (advance) {
        current = iter1.next();
        advance = false;
    }
    if (current2 == null || current > current2) {
        current2 = iter2.next();
    }
    if (current <= current2) {
        advance = true;
        if (current == current2)
            iter1.remove();
    }
}

Ответ 3

Я подозреваю, что вы удаляете из ArrayList, является хитом, поскольку список может быть разделен, когда элемент в середине удален, или если список должен быть уплотнен после удаления элемента. Это может быть быстрее:

  • Создать "Установить" элементов, которые нужно удалить.
  • Создайте новый результат ArrayList, который вам нужен, назовите его R. Вы можете придать ему достаточный размер при построении.
  • Инициализируйте исходный список, который вам нужно удалить из него, если элемент найден в Set, не добавляйте его в R, иначе добавьте его.

Это должно быть O(N); если создание Set и поиск в нем предполагается постоянным.