Создание отдельного списка из существующего списка в Java 7 и 8?

Если у меня есть:

List<Integer> listInts = { 1, 1, 3, 77, 2, 19, 77, 123, 14, 123... }

в Java, что является эффективным способом создания List<Integer> listDistinctInts, содержащего только отдельные значения из listInts?

Мое непосредственное размышление - создать Set<Integer> setInts, содержащий все значения из listInts, затем вызвать List<Integer> listDistinctInts = new ArrayList<>(setInts);

Но это кажется потенциально неэффективным - есть ли лучшее решение, использующее Java 7?

Я не использую Java 8, но я считаю, что, используя его, я мог бы сделать что-то вроде этого (?):

List<Integer> listDistinctInts = listInts.stream().distinct().collect(Collectors.toList());

Будет ли это более реалистичным, чем подход выше и/или есть ли более эффективный способ сделать это в Java 8?

Наконец, (и я знаю, что задание нескольких вопросов может быть неодобрительно, но напрямую связано), если я только заботился о подсчете различных элементов в listInts, есть ли более эффективный способ получить это значение (в Java 7 и 8) - без предварительного создания списка или набора всех отдельных элементов?

Мне больше всего интересны собственные способы Java для этого и избегание повторного создания каких-либо колес, но рассмотрим ручной код или библиотеки, если они предлагают лучшую четкость или производительность. Я прочитал этот связанный с нами вопрос Java - Distinct List of Objects, но не совсем ясно о различиях в производительности между подходами Java 7 и 8 или может быть лучше методы?

Ответ 1

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

Все мои тесты были выполнены с помощью JMH Java Microbenchmarking Harness.

Большинство из этих тестов были выполнены с использованием JDK 1.8, хотя я также выполнил некоторые тесты с JDK 1.7, чтобы убедиться, что его производительность не слишком отличается (она была почти идентичной). Я тестировал следующие методы, взятые из ответов, представленных до сих пор:


1. Java 8 Stream. Решение с использованием stream() Я пропросил как возможность использовать Java8:

public List<Integer> testJava8Stream(List<Integer> listInts) {
    return listInts.stream().distinct().collect(Collectors.toList());
}

профессионалы современный подход Java 8, отсутствие зависимостей сторонних разработчиков

минус Требуется Java 8


2. Добавление в список - решение, предложенное Victor2748, где создается и добавляется новый список, если и только если список еще не содержит значения, Обратите внимание, что я также предопределяю список адресатов размером оригинала (максимально возможное), чтобы предотвратить перераспределение:

public List<Integer> testAddingToList(List<Integer> listInts) {
    List<Integer> listDistinctInts = new ArrayList<>(listInts.size());
    for(Integer i : listInts)
    {
        if( !listDistinctInts.contains(i) ) { listDistinctInts.add(i); }
    }
    return listDistinctInts;
}

профи Работает в любой версии Java, нет необходимости создавать набор, а затем копировать, а не сторонние отпечатки

cons Требуется многократно проверять список для существующих значений при его создании


3. Коллекции GS Fast (теперь коллекции Eclipse) - Решение, предложенное Крейгом П. Мотлином, используя Библиотека коллекций GS и их собственный тип списка FastList:

public List<Integer> testGsCollectionsFast(FastList listFast)
{
    return listFast.distinct();
}

профи Сообщается, что очень быстрый, простой выразительный код работает в Java 7 и 8

минус Требуется сторонняя библиотека и FastList, а не обычный List<Integer>


4. Коллекции GS адаптированы. Решение FastList не совсем сравнивалось как-будто, потому что ему нужен был FastList, а не хороший ol ArrayList<Integer>, поэтому я также протестировал метод адаптера, предложенный Крейгом

public List<Integer> testGsCollectionsAdapted(List<Integer> listInts)
{
    return listAdapter.adapt(listInts).distinct();
}

профи Не требуется FastList, работает в Java 7 и 8

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


5. Guava ImmutableSet - метод, предложенный Луи Вассерманом в комментариях, и 卢 声 远 Shengyuan Lu в их ответе, используя Guava:

public List<Integer> testGuavaImmutable(List<Integer> listInts)
{
    return ImmutableSet.copyOf(listInts).asList();
}

профи Сообщается очень быстро, работает в Java 7 или 8

cons Возвращает Immutable List, не может обрабатывать нули во входном списке и требует стороннюю библиотеку


7. HashSet - моя оригинальная идея (также рекомендуется EverV0id, ulix и Radiodef)

public List<Integer> testHashSet(List<Integer> listInts)
{
    return new ArrayList<Integer>(new HashSet<Integer>(listInts));
}

профи Работает в Java 7 и 8, без зависимостей сторонних разработчиков

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


6. LinkedHashSet. Поскольку решение HashSet не сохраняло порядок целых чисел в исходном списке, я также протестировал версию, которая использует LinkedHashSet для сохранения порядка:

public List<Integer> testLinkedHashSet(List<Integer> listInts)
{
    return new ArrayList<Integer>(new LinkedHashSet<Integer>(listInts));
}

профи Сохраняет первоначальный заказ, работает в Java 7 и 8, не имеет сторонних зависимостей

минус Маловероятно, чтобы он был таким же быстрым, как обычный HashSet подход


Результаты

Вот мои результаты для разных размеров listInts (результаты заказываются от самого медленного до самого быстрого):

1. отличаясь от ArrayList 100 000 случайных чисел от 0 до 50 000 (т.е. большой список, некоторые дубликаты)

Benchmark                Mode       Samples     Mean   Mean error    Units

AddingToList            thrpt        10        0.505        0.012    ops/s
Java8Stream             thrpt        10      234.932       31.959    ops/s
LinkedHashSet           thrpt        10      262.185       16.679    ops/s
HashSet                 thrpt        10      264.295       24.154    ops/s
GsCollectionsAdapted    thrpt        10      357.998       18.468    ops/s
GsCollectionsFast       thrpt        10      363.443       40.089    ops/s
GuavaImmutable          thrpt        10      469.423       26.056    ops/s

2. принимая отличные от ArrayList 1000 случайных чисел между 0-50 (т.е. средний список, много дубликатов)

Benchmark                Mode       Samples     Mean   Mean error    Units

AddingToList            thrpt        10    32794.698     1154.113    ops/s
HashSet                 thrpt        10    61622.073     2752.557    ops/s
LinkedHashSet           thrpt        10    67155.865     1690.119    ops/s
Java8Stream             thrpt        10    87440.902    13517.925    ops/s
GsCollectionsFast       thrpt        10   103490.738    35302.201    ops/s
GsCollectionsAdapted    thrpt        10   143135.973     4733.601    ops/s
GuavaImmutable          thrpt        10   186301.330    13421.850    ops/s

3. отличаясь от ArrayList из 100 случайных чисел от 0 до 100 (т.е. небольшой список, некоторые дубликаты)

Benchmark                Mode       Samples     Mean   Mean error    Units

AddingToList            thrpt        10   278435.085    14229.285    ops/s
Java8Stream             thrpt        10   397664.052    24282.858    ops/s
LinkedHashSet           thrpt        10   462701.618    20098.435    ops/s
GsCollectionsAdapted    thrpt        10   477097.125    15212.580    ops/s
GsCollectionsFast       thrpt        10   511248.923    48155.211    ops/s
HashSet                 thrpt        10   512003.713    25886.696    ops/s
GuavaImmutable          thrpt        10  1082006.560    18716.012    ops/s

4. принимая отличные от ArrayList 10 случайных чисел между 0-50 (т.е. крошечный список, несколько дубликатов)

Benchmark                Mode       Samples     Mean   Mean error    Units

Java8Stream             thrpt        10  2739774.758   306124.297    ops/s
LinkedHashSet           thrpt        10  3607479.332   150331.918    ops/s
HashSet                 thrpt        10  4238393.657   185624.358    ops/s
GsCollectionsAdapted    thrpt        10  5919254.755   495444.800    ops/s
GsCollectionsFast       thrpt        10  7916079.963  1708778.450    ops/s
AddingToList            thrpt        10  7931479.667   966331.036    ops/s
GuavaImmutable          thrpt        10  9021621.880   845936.861    ops/s

Выводы

  • Если вы принимаете только отдельные элементы из списка один раз, а список не очень длинный, любой из этих методов должен быть адекватным.

  • Наиболее эффективные общие подходы поступали от сторонних библиотек: GS Collections и Guava выполнялись превосходно.

  • При выборе наиболее эффективного метода вам может потребоваться размер вашего списка и вероятное количество дубликатов.

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

  • Метод Guava ImmutableSet.copyOf(listInts).asList() работает наиболее быстро в большинстве ситуаций. Но обратите внимание на ограничения: возвращенный список Immutable, и список ввода не может содержать нули.

  • Метод HashSet выполняет лучшие из сторонних подходов и обычно лучше, чем потоки Java 8, но переупорядочивает целые числа (что может быть или не быть проблемой в зависимости от вашего прецедента).

  • Подход LinkedHashSet сохраняет порядок, но неудивительно, как правило, хуже, чем метод HashSet.

  • Оба метода HashSet и LinkedHashSet будут хуже работать при использовании списков типов данных, которые имеют сложные вычисления HashCode, поэтому сделайте свое собственное профилирование, если вы пытаетесь выбрать отдельный Foo из List<Foo>.

  • Если у вас уже есть GS Collections как зависимость, то она работает очень хорошо и более гибкая, чем ImmutableList Guava. Если у вас нет его как зависимости, стоит подумать над его добавлением, если производительность выбора отдельных элементов имеет решающее значение для производительности вашего приложения.

  • Неутешительно потоки Java 8, казалось, выполнялись довольно плохо. Может быть лучший способ закодировать вызов distinct(), чем тот, который я использовал, поэтому комментарии и другие ответы, конечно, приветствуются.

NB. Я не эксперт в MicroBenchmarking, поэтому, если кто-либо обнаружит недостатки в моих результатах или методологии, пожалуйста, сообщите мне, и я постараюсь исправить ответ.

Ответ 2

Если вы используете Коллекции Eclipse (ранее Коллекции GS), вы можете использовать метод distinct().

ListIterable<Integer> listInts = FastList.newListWith(1, 1, 3, 77, 2, 19, 77, 123, 14, 123);
Assert.assertEquals(
        FastList.newListWith(1, 3, 77, 2, 19, 123, 14),
        listInts.distinct());

Преимущество использования distinct() вместо преобразования в набор, а затем обратно в список состоит в том, что distinct() сохраняет порядок исходного списка, сохраняя первое вхождение каждого элемента. Он реализован с использованием как Set, так и List.

MutableSet<T> seenSoFar = UnifiedSet.newSet();
int size = list.size();
for (int i = 0; i < size; i++)
{
    T item = list.get(i);
    if (seenSoFar.add(item))
    {
        targetCollection.add(item);
    }
}
return targetCollection;

Если вы не можете преобразовать исходный список в тип коллекций GS, вы можете использовать ListAdapter для получения того же API.

MutableList<Integer> distinct = ListAdapter.adapt(integers).distinct();

Невозможно избежать создания набора. Тем не менее, UnifiedSet более эффективен, чем HashSet, поэтому будет некоторая скорость.

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

Verify.assertSize(7, UnifiedSet.newSet(listInts));

Eclipse Collections 8.0 требует Java 8. Eclipse Collections 7.x хорошо работает с Java 8, но требует только Java 5.

Примечание. Я являюсь коммиттером для коллекций Eclipse.

Ответ 3

Guava может быть на ваш выбор:

ImmutableSet<Integer> set = ImmutableSet.copyOf(listInts);

API чрезвычайно оптимизирован.

Это БЫСТРО, чем listInts.stream().distinct() и new LinkedHashSet<>(listInts).

Ответ 4

При добавлении значения к значению listInts:

int valueToAdd;
//...
if (!listInts.contains(valueToAdd)) {listInts.add(valueToAdd)}

если у вас есть существующий список, используйте оператор for-each для копирования всех значений из этого списка на новый, который вы хотите "отличить":

List<Integer> listWithRepeatedValues;
List<Integer> distinctList;
//...
for (Integer i : listWithRepeatedValues) {
    if (!listInts.contains(valueToAdd)) {distinctList.add(i);}
}

Ответ 5

Вам следует попробовать new LinkedList(new HashSet(listInts)).

Ответ 6

Не волнуйся. Использование HashSet - довольно простой и эффективный способ устранения дубликатов:

    Set<Integer> uniqueList = new HashSet<>();
    uniqueList.addAll(listInts);   // Add all elements eliminating duplicates

    for (int n : uniqueList)       // Check the results (in no particular order)
        System.out.println(n);

    System.out.println("Number distinct values: " + uniqueList.size());

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

    boolean[] counterTable = new boolean[124];
    int counter = 0;

    for (int n : listInts)
        if (!counterTable[n]) {
            counter++;
            counterTable[n] = true;
        }

    System.out.println("Number of distinct values: " + counter);

Ответ 7

Это должно работать:

yourlist.stream(). map (ваша оболочка, которая переопределяет значения equals и hashchode method:: new).distinct(). map (оболочка, определенная выше:: метод, возвращающий окончательный вывод).collect(Collectors.toList() );