【问题标题】:Extract duplicate objects from a List in Java 8从 Java 8 中的列表中提取重复对象
【发布时间】:2019-04-09 10:29:40
【问题描述】:

此代码从原始列表中删除重复项,但我想从原始列表中提取重复项 -> 不删除它们(此包名称只是另一个项目的一部分):

给定:

一个人 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();
}
}

重复提取码。

注意这里我们过滤了 id 和名字来检索一个新列表,我在其他地方看到了这段代码,不是我的:

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);

}

}

在我的工作中,我能够通过使用 TreeMap 和 ArrayList 的 Comparator 来获得所需的结果,但这是创建一个列表然后过滤它,再次将过滤器传递给新创建的列表,这看起来很臃肿的代码,(并且可能效率低下)

有人对如何提取重复项有更好的想法吗?而不是删除它们。

提前致谢。

更新

感谢大家的回答

使用与 uniqueAttributes 相同的方法删除重复项:

  public static List<Person> removeDuplicates(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(DuplicatePersonFilter::uniqueAttributes));
}

private static String uniqueAttributes(Person person){

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

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

更新 2

但@brett-ryan 提供的答案也是正确的:

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

        List<Person> duplicates = personList.stream()
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                .entrySet().stream()
                .filter(n -> n.getValue() > 1)
                .flatMap(n -> nCopies(n.getValue().intValue(), n.getKey()).stream())
                .collect(toList());

        return duplicates;

    }

编辑

上面的代码可以在下面找到:

https://gitlab.com/totopoloco/marco_utilities/-/tree/master/duplicates_exercises

请看:

用法: https://gitlab.com/totopoloco/marco_utilities/-/blob/master/duplicates_exercises/src/test/java/at/mavila/exercises/duplicates/lists/DuplicatePersonFilterTest.java

实施: https://gitlab.com/totopoloco/marco_utilities/-/blob/master/duplicates_exercises/src/main/java/at/mavila/exercises/duplicates/lists/DuplicatePersonFilter.java

【问题讨论】:

  • 我认为你在这里发布了太多代码。如果可能,通常将其保持在 10-20 行。有人可能很难解析你在这里所做的事情。
  • 改为 [romualdo, otroRomualdo...另一个甚至不在列表中。
  • @nullpointer 完全正确,不知道为什么会为一个非常不清楚的问题投票
  • 对不起,如果我不清楚,我们在列表中有5个对象,通过该方法将返回前4个元素,好的,但是我们想获取元素3和4(从开始计数0),为什么?,因为这两个在列表中是重复的。正如我所说,不知何故,我通过使用 Comparator、TreeMap 从列表中提取列表得到了结果,但我的代码看起来真的很臃肿,我可以分享它,但我在另一台笔记本电脑上有它。我一到办公室就分享。
  • 在我的工作中,我能够通过使用 TreeMap 和 ArrayList 的 Comparator 来获得所需的结果,但这是创建一个列表然后对其进行过滤 ....在哪里那个代码?我宁愿把它和解释一起放在这里,而不是你目前分享的内容。

标签: java list java-8 duplicates java-stream


【解决方案1】:

要识别重复项,我知道没有比Collectors.groupingBy() 更适合的方法。这允许您根据您选择的条件将列表分组到地图中。

您的条件是idfirstName 的组合。让我们将这部分提取到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() 用于将列表流扁平化为单个人员流,并将流收集到列表中。

如果你真正确定人是平等的,如果idfirstName 相同,则另一种方法是使用the solution by Jonathan Johx 并实现equals() 方法。

【讨论】:

  • 实际上这也是一个很好的解决方案,删除重复项是相同的方法。
  • 我会给这个解决方案标签,因为这是我最后实现的。
【解决方案2】:

如果您可以在Person 上实现equalshashCode,则可以使用groupingBy 的计数下游收集器来获取已重复的不同元素。

List<Person> duplicates = personList.stream()
  .collect(groupingBy(identity(), counting()))
  .entrySet().stream()
  .filter(n -> n.getValue() > 1)
  .map(n -> n.getKey())
  .collect(toList());

如果您想保留一个连续重复元素的列表,您可以使用Collections.nCopies 将其展开以将其展开。此方法将确保重复的元素排列在一起。

List<Person> duplicates = personList.stream()
    .collect(groupingBy(identity(), counting()))
    .entrySet().stream()
    .filter(n -> n.getValue() > 1)
    .flatMap(n -> nCopies(n.getValue().intValue(), n.getKey()).stream())
    .collect(toList());

【讨论】:

  • 此代码 sn-p 在提取重复项的单个元素的方式上是“正确的”,例如假设您的列表有 5 个重复的“a”和 3 个重复的“b”,您的 sn-p 返回一个仅包含元素“a”和“b”的列表。
  • 但是要求很明确,如果我们用同样的例子,我们需要返回一个新的5个“a”和3个“b”的列表,给出一个8个元素的列表长度,但是正如我之前提到的,您的代码 sn-p 返回一个只有 2 个元素的列表,所以它不好,@Magnilex 给出的答案仍然是正确的。
  • 我很抱歉@MarcoTulioAvilaCerón,但在您提供的原始帖子中看起来并不那么干净。
  • 没问题,我将所有答案都放在 Gitlab 的这篇文章中:gitlab.com/totopoloco/marco_utilities/-/tree/master/…
  • 我已经更新了一个使用 Collections.nCopies 的解决方案。您也可以使用 IntStream.range(n, obj) 来实现这一点,但它的可读性会降低。
【解决方案3】:
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 已重复。

【讨论】:

  • 好的,如果我们链接到按名字分组的相同机制就可以工作
  • 其他人已经解决了这个问题,例如,用@Magnilex 答案中的Person::uniqueAttributes 替换Person::getId 应该可以做到这一点。
  • 这个很容易阅读和实现
【解决方案4】:

在这种情况下,您需要编写自定义逻辑以从列表中提取重复项,您将获得 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]]

【讨论】:

    【解决方案5】:

    我认为首先你应该覆盖 Person 类的 equals 方法并专注于 id 和 name。并且在您可以更新它之后添加一个过滤器。

    @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());
    

    【讨论】:

    • - 当你覆盖 equals 时,你需要覆盖 hashcode。
    • - 什么是“Proveedor”。
    • 对不起,我在睡觉 xD 没有那条线 xD 是的!哈希码也是如此。我更新了,谢谢。
    【解决方案6】:

    基于通用键的解决方案:

    public static <T> List<T> findDuplicates(List<T> list, Function<T, ?> uniqueKey) {
        if (list == null) {
            return emptyList();
        }
        Function<T, ?> notNullUniqueKey = el -> uniqueKey.apply(el) == null ? "" : uniqueKey.apply(el);
        return list.stream()
                .collect(groupingBy(notNullUniqueKey))
                .values()
                .stream()
                .filter(matches -> matches.size() > 1)
                .map(matches -> matches.get(0))
                .collect(toList());
    }
    
    
    // Example of usage:
    List<Person> duplicates = findDuplicates(list, el -> el.getFirstName());
    

    【讨论】:

      【解决方案7】:
      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);
      

      上面的代码会根据name和id进行过滤。结果 List 将包含所有重复的 Person 对象

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-06-18
        • 1970-01-01
        • 1970-01-01
        • 2021-01-08
        • 1970-01-01
        • 1970-01-01
        • 2018-07-13
        • 1970-01-01
        相关资源
        最近更新 更多