Извлечение дубликатов объектов из списка в Java 8 - PullRequest
0 голосов
/ 06 ноября 2018

Этот код удаляет дубликаты из исходного списка, но я хочу извлечь дубликаты из исходного списка -> не удаляя их (это имя пакета является просто частью другого проекта):

Дано:

Человек pojo:

package at.mavila.learn.kafka.kafkaexercises;

import org.apache.commons.lang3.builder.ToStringBuilder;

public class Person {

private final Long id;
private final String firstName;
private final String secondName;


private Person(final Builder builder) {
    this.id = builder.id;
    this.firstName = builder.firstName;
    this.secondName = builder.secondName;
}


public Long getId() {
    return id;
}

public String getFirstName() {
    return firstName;
}

public String getSecondName() {
    return secondName;
}

public static class Builder {

    private Long id;
    private String firstName;
    private String secondName;

    public Builder id(final Long builder) {
        this.id = builder;
        return this;
    }

    public Builder firstName(final String first) {
        this.firstName = first;
        return this;
    }

    public Builder secondName(final String second) {
        this.secondName = second;
        return this;
    }

    public Person build() {
        return new Person(this);
    }


}

@Override
public String toString() {
    return new ToStringBuilder(this)
            .append("id", id)
            .append("firstName", firstName)
            .append("secondName", secondName)
            .toString();
}
}

Код извлечения дубликата.

Обратите внимание, здесь мы фильтруем идентификатор и имя, чтобы получить новый список, я видел этот код где-то еще, а не мой:

package at.mavila.learn.kafka.kafkaexercises;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static java.util.Objects.isNull;

public final class DuplicatePersonFilter {


private DuplicatePersonFilter() {
    //No instances of this class
}

public static List<Person> getDuplicates(final List<Person> personList) {

   return personList
           .stream()
           .filter(duplicateByKey(Person::getId))
           .filter(duplicateByKey(Person::getFirstName))
           .collect(Collectors.toList());

}

private static <T> Predicate<T> duplicateByKey(final Function<? super T, Object> keyExtractor) {
    Map<Object,Boolean> seen = new ConcurrentHashMap<>();
    return t -> isNull(seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE));

}

}

Тестовый код. Если вы запустите этот тестовый пример, вы получите [alex, lolita, elpidio, romualdo].

Я ожидал бы получить вместо этого [romualdo, otroRomualdo] в качестве извлеченных дубликатов, заданных id и firstName:

package at.mavila.learn.kafka.kafkaexercises;


import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

public class DuplicatePersonFilterTest {

private static final Logger LOGGER = LoggerFactory.getLogger(DuplicatePersonFilterTest.class);



@Test
public void testList(){

    Person alex = new Person.Builder().id(1L).firstName("alex").secondName("salgado").build();
    Person lolita = new Person.Builder().id(2L).firstName("lolita").secondName("llanero").build();
    Person elpidio = new Person.Builder().id(3L).firstName("elpidio").secondName("ramirez").build();
    Person romualdo = new Person.Builder().id(4L).firstName("romualdo").secondName("gomez").build();
    Person otroRomualdo = new Person.Builder().id(4L).firstName("romualdo").secondName("perez").build();


    List<Person> personList = new ArrayList<>();

    personList.add(alex);
    personList.add(lolita);
    personList.add(elpidio);
    personList.add(romualdo);
    personList.add(otroRomualdo);

    final List<Person> duplicates = DuplicatePersonFilter.getDuplicates(personList);

    LOGGER.info("Duplicates: {}",duplicates);

}

}

В своей работе я смог получить желаемый результат с помощью Comparator, используя TreeMap и ArrayList, но это было создание списка, затем его фильтрация, повторная передача фильтра во вновь созданный список, это выглядит раздутым кодом (и вероятно, неэффективно)

Кто-нибудь лучше знает, как извлечь дубликаты? Не удалять их.

Заранее спасибо.

Обновление :

Спасибо всем за ваши ответы

Чтобы удалить дубликаты, используя тот же подход с уникальными атрибутами:

 public static List<Person> removeDuplicates(final List<Person> personList) {

    return personList.stream().collect(Collectors
            .collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(
                    PersonListFilters::uniqueAttributes))),
                    ArrayList::new));

}

 private static String uniqueAttributes(Person person){

    if(Objects.isNull(person)){
        return StringUtils.EMPTY;
    }



    return (person.getId()) + (person.getFirstName()) ;
}

Ответы [ 5 ]

0 голосов
/ 06 ноября 2018

Для идентификации дубликатов ни один из известных мне методов не подходит лучше, чем Collectors.groupingBy(). Это позволяет вам сгруппировать список в карту на основе выбранного вами условия.

Ваше состояние - комбинация id и firstName. Давайте извлечем эту часть в собственный метод в Person:

String uniqueAttributes() {
  return id + firstName;
}

Метод getDuplicates() теперь довольно прост:

public static List<Person> getDuplicates(final List<Person> personList) {
  return getDuplicatesMap(personList).values().stream()
      .filter(duplicates -> duplicates.size() > 1)
      .flatMap(Collection::stream)
      .collect(Collectors.toList());
}

private static Map<String, List<Person>> getDuplicatesMap(List<Person> personList) {
  return personList.stream().collect(groupingBy(Person::uniqueAttributes));
}
  • Первая строка вызывает другой метод getDuplicatesMap() для создания карты, как описано выше.
  • Затем он перемещается по значениям карты, которые являются списками лиц.
  • Отфильтровывает все, кроме списков с размером больше 1, то есть находит дубликаты.
  • Наконец, flatMap() используется для сглаживания потока списков в один отдельный поток людей и сбора потока в список.

Альтернатива, если вы действительно идентифицируете людей как равных, если они имеют одинаковые id и firstName, состоит в том, чтобы пойти с решением Джонатана Джохса и реализовать метод equals().

0 голосов
/ 06 ноября 2018
List<Person> arr = new ArrayList<>();
arr.add(alex);
arr.add(lolita);
arr.add(elpidio);
arr.add(romualdo);
arr.add(otroRomualdo);

Set<String> set = new HashSet<>();
List<Person> result = arr.stream()
                         .filter(data -> (set.add(data.name +";"+ Long.toString(data.id)) == false))
                         .collect(Collectors.toList());
arr.removeAll(result);
Set<String> set2 = new HashSet<>();
result.stream().forEach(data -> set2.add(data.name +";"+ Long.toString(data.id)));
List<Person> resultTwo = arr.stream()
                            .filter(data -> (set2.add(data.name +";"+ Long.toString(data.id)) == false))
                            .collect(Collectors.toList());
result.addAll(resultTwo);

Приведенный выше код будет фильтровать по имени и идентификатору. Список результатов будет иметь все дублированные объекты Person

0 голосов
/ 06 ноября 2018
List<Person> duplicates = personList.stream()
  .collect(Collectors.groupingBy(Person::getId))
  .entrySet().stream()
  .filter(e->e.getValue().size() > 1)
  .flatMap(e->e.getValue().stream())
  .collect(Collectors.toList());

Это должно дать вам список Person, где id был продублирован.

0 голосов
/ 06 ноября 2018

Я думаю, что сначала вы должны переписать метод equals класса Person и сосредоточиться на идентификаторе и имени. И после вы можете обновить его, добавив фильтр для этого.

@Override
public int hashCode() {
    return Objects.hash(id, name);
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    final Person other = (Person) obj;
    if (!Objects.equals(name, other.name)) {
        return false;
    }
    if (!Objects.equals(id, other.id)) {
        return false;
    }
    return true;
}

 personList
       .stream() 
       .filter(p -> personList.contains(p))
       .collect(Collectors.toList());
0 голосов
/ 06 ноября 2018

В этом сценарии вам нужно написать свою собственную логику для извлечения дубликатов из списка, вы получите все дубликаты в Person списке

   public static List<Person> extractDuplicates(final List<Person> personList) {

    return personList.stream().flatMap(i -> {
        final AtomicInteger count = new AtomicInteger();
        final List<Person> duplicatedPersons = new ArrayList<>();

        personList.forEach(p -> {

            if (p.getId().equals(i.getId()) && p.getFirstName().equals(i.getFirstName())) {
                count.getAndIncrement();
            }

            if (count.get() == 2) {
                duplicatedPersons.add(i);
            }

        });

        return duplicatedPersons.stream();
    }).collect(Collectors.toList());
}

Применительно к:

 List<Person> l = new ArrayList<>();
           Person alex = new 
 Person.Builder().id(1L).firstName("alex").secondName("salgado").build();
            Person lolita = new 
 Person.Builder().id(2L).firstName("lolita").secondName("llanero").build();
            Person elpidio = new 
 Person.Builder().id(3L).firstName("elpidio").secondName("ramirez").build();
            Person romualdo = new 
 Person.Builder().id(4L).firstName("romualdo").secondName("gomez").build();
            Person otroRomualdo = new 
 Person.Builder().id(4L).firstName("romualdo").secondName("perez").build();
      l.add(alex);
      l.add(lolita);
      l.add(elpidio);
      l.add(romualdo);
      l.add(otroRomualdo);

Выход:

[Person [id=4, firstName=romualdo, secondName=gomez], Person [id=4, firstName=romualdo, secondName=perez]]
...