Возвращайте только уникальные значения из коллекции, удаляя все повторы

Предположим, у меня есть некоторая коллекция, как я могу вернуть только те значения, которые появляются ровно один раз? Я не хочу сокращать повторяющиеся значения до одной записи, я хочу полностью их исключить — они не подходят для моего варианта использования.

Например, список, содержащий 1, 2, 3, 1, 4, 3, 5, 1, должен возвращать только 2, 4, 5. Я мог бы просмотреть список, подсчитать элементы, а затем удалить все, что появляется более одного раза, но мне интересно, есть ли .stream() решение, которое мне не хватает.

Для небольших списков: list.stream().filter(i -> Collections.frequency(list, i) == 1).toList();

Asmir 12.07.2024 22:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
142
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Чтобы вернуть только те элементы, которые появляются ровно один раз, вы можете использовать Stream для достижения этой цели:

List<Integer> list = Arrays.asList(1, 2, 3, 1, 4, 3, 5, 1);

//Find Number of occurence of each element in the List 

Map<Integer, Long> counts = list.stream()
    .collect(Collectors.groupingBy(e -> e, Collectors.counting()));

// Filter elements that appear only once

List<Integer> result = list.stream()
    .filter(element -> counts.get(element) == 1)
    .toList();

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

Chaosfire 12.07.2024 17:52

возможно list.stream().collect(groupingBy(identity(), counting())).entrySet().stream().filter(e -> e.getValue()==1).map(Map.Entry::getKey).toList()

user85421 12.07.2024 18:48

Для чего-то подобного я предпочитаю императивный подход с использованием наборов.

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

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

List<Integer> list = 
         new ArrayList<>(Arrays.asList(1, 2, 3, 1, 4, 3, 5, 1));
System.out.println("Original list = " + list);

Set<Integer> seen = new HashSet<>();
Set<Integer> result = new HashSet<>();
for(int v : list) {
     result.add(v);  // try and add the value
     if (!seen.add(v)) { // if already seen, remove it.
         result.remove(v);
     }
}

System.out.println("Result = " + result);

принты

Original list = [1, 2, 3, 1, 4, 3, 5, 1]
Result = [2, 4, 5]

При необходимости набор результатов можно затем поместить в список.

Потоковая версия

Вот поток, эквивалентный приведенному выше.

  • он использует запись для хранения наборов seen и result.
  • он оборачивает результирующий набор в список.
 record Info(Set<Integer> seen, Set<Integer> result){}

 List<Integer> resultList =
      new ArrayList<>(list.stream().reduce(
         new Info(new HashSet<>(), new HashSet<>()), 
         (info, val) -> {
             info.result.add(val);
             if (!info.seen.add(val)) {
                 info.result.remove(val);
             }
             return info;
         },
         (a, b) -> a).result);
 System.out.println(resultList);
 

Если бы я делал это, я бы придерживался императивного решения, поскольку оно проще, а потоковое решение на самом деле не более декларативное, но более сложное.

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

List<Integer> unique = list.stream()
    .filter(Predicate.not(list.stream().filter(Predicate.not(new HashSet<>()::add)).collect(Collectors.toSet())::contains))
    .toList();

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

Что происходит выше, станет понятнее, если провести рефакторинг, выделив промежуточные этапы как локальные переменные:

// Define a set to record all seen integers:
Set<Integer> seen = new HashSet<>();

// Create a set containing all duplicated integers
// this relies on seen.add(x) returning false for each duplicate  item:
Set<Integer> dups = list.stream()
                        .filter(Predicate.not(seen::add))
                        .collect(Collectors.toSet());

// Finally scan the list and eliminate every entry which is a duplicate:
List<Integer> unique = list.stream()
    .filter(Predicate.not(dups::contains))
    .toList();

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