Как лучше всего удалить дубликаты в массиве в Java?

У меня есть массив объектов, дубликаты которых нужно удалить / отфильтровать. Я собирался просто переопределить equals & hachCode в элементах Object, а затем вставить их в Set ... но я решил, что должен хотя бы опросить stackoverflow, чтобы узнать, есть ли другой способ, возможно, какой-то умный метод какого-то другого API?

Зачем ставить себя в это место? Почему бы в первую очередь не предотвратить дублирование?

LeppyR64 10.12.2008 23:11

Задайте вопрос об удалении дубликатов ... получите кучу повторяющихся ответов. Ирония!

erickson 10.12.2008 23:19

То, как вы описываете, идеально.

OscarRyz 11.12.2008 03:35
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
15
3
40 438
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Ответ принят как подходящий

Я согласен с вашим подходом к переопределению hashCode() и equals() и использованию чего-то, что реализует Set.

Это также делает абсолютно ясным для всех других разработчиков, что требуется не дублирующая характеристика.

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

и вам не нужно менять свой код, чтобы изменить реализацию в будущем.

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

Моей первой мыслью тоже было переопределение equals и hashCode и создание набора. В любом случае рекомендуется иметь в иерархии наследования некоторую переопределенную версию этих методов.

Я считать, что если вы используете LinkedHashSet, вы даже сохраните порядок уникальных элементов ...

Да, LinkedHashSet сохранит порядок вставки.

Ken Gentle 10.12.2008 23:20

Не рекомендуется переопределять equals и hashCode «в любом случае», особенно в любом классе, который будет находиться в иерархии наследования. См. Эффективная Java (Блох) для получения дополнительной информации.

McDowell 11.12.2008 00:00

Макдауэлл, я не совсем понял - я имел в виду, что в вашей иерархии наследования должна быть переопределенная версия где-то, и я изменил ответ, чтобы отразить это. У меня нет копии «Эффективной Java» - это к чему Блох?

Dan Vinton 12.12.2008 02:53

Я нашел это в сети

Вот два метода, которые позволяют удалять дубликаты в ArrayList. removeDuplicate не поддерживает порядок, в то время как removeDuplicateWithOrder поддерживает порядок с некоторыми издержками производительности.

  1. Метод removeDuplicate:

    /** List order not maintained **/
    public static void removeDuplicate(ArrayList arlList)
    {
     HashSet h = new HashSet(arlList);
     arlList.clear();
     arlList.addAll(h);
    }
    
  2. Метод removeDuplicateWithOrder:

    /** List order maintained **/
    public static void removeDuplicateWithOrder(ArrayList arlList)
    {
       Set set = new HashSet();
       List newList = new ArrayList();
       for (Iterator iter = arlList.iterator(); iter.hasNext();) {
          Object element = iter.next();
          if (set.add(element))
             newList.add(element);
       }
       arlList.clear();
       arlList.addAll(newList);
    }
    

Хороший ответ, но ваш второй пример не находится в блоке формата кода

TravisO 10.12.2008 23:24

спасибо Ken G ... я пробовал это пару раз, но я не смог исправить второй блог кода

Markus Lausberg 10.12.2008 23:28

LinkedHashSet держит его в порядке. Это может еще больше его оптимизировать.

Daddy Warbox 12.12.2008 02:55

Исходя из общего стандарта программирования, вы всегда можете дважды перечислить коллекции, а затем сравнить источник и цель.

И если ваше внутреннее перечисление всегда начинается с одной записи после источника, это довольно эффективно (псевдокод для подражания)

foreach ( array as source )
{
    // keep track where we are in the array
    place++;
    // loop the array starting at the entry AFTER the current one we are comparing to
    for ( i=place+1; i < max(array); i++ )
    {
        if ( source === array[place] )
        {
            destroy(array[i]);
        }
    }
}

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

Я хотел бы повторить мысль, высказанную Джейсоном в комментариях:

Зачем вообще ставить себя в эту точку?

Зачем использовать массив для структуры данных, в которой вообще не должно быть дубликатов?

Всегда используйте Set или SortedSet (когда элементы тоже имеют естественный порядок), чтобы удерживать элементы. Если вам нужно сохранить порядок вставки, вы можете использовать LinkedHashSet, как было указано.

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

Я согласен со всеми комментариями по поводу того, что исходная структура данных является массивом. Я пытаюсь убедить разработчика провести рефакторинг до Set. Спасибо всем за отзывы и мудрость!

Liggy 11.12.2008 18:10

Конечно, в исходном сообщении возникает вопрос: «Как вы вообще получили этот массив (который может содержать повторяющиеся записи)?»

Вам нужен массив (с дубликатами) для других целей, или вы могли бы просто использовать Set с самого начала?

В качестве альтернативы, если вам нужно знать количество появлений каждого значения, вы можете использовать Map<CustomObject, Integer> для отслеживания счетчиков. Также может оказаться полезным определение Коллекции Google классов Multimap.

По сути, вам нужна реализация LinkedHashSet<T>, которая поддерживает интерфейс List<T> для произвольного доступа. Следовательно, это то, что вам нужно:

public class LinkedHashSetList<T> extends LinkedHashSet<T> implements List<T> {

// Implementations for List<T> methods here...

}

Реализация методов List<T> будет обращаться к лежащему в основе LinkedHashSet<T> и манипулировать им. Хитрость заключается в том, чтобы этот класс вел себя правильно, когда кто-то пытается добавить дубликаты с помощью методов добавления List<T> (выбрасывание исключения или повторное добавление элемента в другой индекс будут вариантами: которые вы можете либо выбрать один из, либо сделать настраиваемым пользователями класса).

Используйте список distinctList для записи элемента, когда iterator впервые сталкивается с ним, возвращает отдельный список, поскольку список удаляет все дубликаты

 private List removeDups(List list) {
        Set tempSet = new HashSet();
        List distinctList = new ArrayList();
        for(Iterator  it = list.iterator(); it.hasNext();) {
            Object next = it.next();
            if (tempSet.add(next)) {
                distinctList.add(next);
            } 
        }
        return distinctList;
   } 

сложность очень высока, поскольку List.contains имеет временную сложность O (n), поэтому сложность составляет O (N ^ 2)

Filip Luchianenco 25.09.2018 07:33

@FilipLuchianenco вы правы, я обновил свою реализацию

didxga 25.09.2018 10:08

Затем вам нужно только продолжать добавлять новое значение, если оно существует, оно просто вернет false. В результате вы получите итератор и Set, в которые вы продолжаете добавлять уникальные значения. Единственным недостатком является порядок, поскольку Set не сохраняет его из-за изменения размера. Тогда другое решение состоит в том, чтобы иметь список и набор, и если ваш set.add (object) возвращает true, вы также добавляете его в новый список; затем верните список.

Filip Luchianenco 27.09.2018 18:52

Почему нам нужно заботиться о порядке набора, поскольку мы собираемся изменить список, переданный в функции, который мы используем итератор для удаления дубликатов, который не меняет внутренний порядок списка

didxga 30.09.2018 17:15

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

Filip Luchianenco 01.10.2018 06:45

рефакторинг, хотя я сомневаюсь, что временная сложность была O (n ^ k), потому что временная сложность ArrayList.remove (i) равна n, тогда наихудший случай - O (n ^ 2).

didxga 03.10.2018 16:40

выглядит отлично! Причина, по которой я сказал, что это n ^ k, состоит в том, что вы вызываете .remove k раз, где k - количество повторяющихся элементов. Так что это не n ^ 2 и не n ^ arraySize, как я уже говорил. В худшем случае, если все элементы одинаковы, то это будет n ^ arraySize. Кроме того, не забудьте упомянуть, что методы equals и hashCode должны быть переопределены и записаны правильно для используемых объектов, иначе он будет продолжать добавлять одни и те же элементы для установки, поскольку хэш-код будет отличаться для двух объектов с одинаковым содержимым, если не переопределить его.

Filip Luchianenco 03.10.2018 18:57

да, OP упомянул, что он собирается переопределить equals и hashCode. Когда вы говорите n ^ arraySize, вы имеете в виду n * arraySize?

didxga 04.10.2018 10:40

на самом деле вы правы, худший случай, учитывая, что решение было O (N ^ 2), теперь это O (N).

Filip Luchianenco 12.10.2018 09:10

Другие вопросы по теме