简化java流以查找重复的属性

简化java流以查找重复的属性,java,java-stream,Java,Java Stream,我有一个用户列表,我想找到所有名称重复的用户: var allNames = users .stream() .map(u -> u.getName()).collect(Collectors.toList()); var duplicateNames = allNames .stream() .filter(i -> Collections.frequency(

我有一个
用户
列表,我想找到所有名称重复的用户:

var allNames = users
              .stream()
              .map(u -> u.getName()).collect(Collectors.toList());

var duplicateNames = allNames
                .stream()
                .filter(i -> Collections.frequency(allNames, i) > 1)
                .collect(Collectors.toSet());
我可以改进/简化上述解决方案吗


例如,实际上我创建了一个包含所有名称的列表,然后对其进行筛选。如何遍历列表以查找其重复名称,而不创建其他列表
allNames

按名称分组,查找具有多个值的条目:

Map<String, List<User>> grouped = users.stream()
    .collect(groupingBy(User::getName));

List<User> duplicated =
    grouped.values().stream()
        .filter(v -> v.size() > 1)
        .flatMap(List::stream)
        .collect(toList());
Map group=users.stream()
.collect(groupingBy(User::getName));
重复列表=
grouped.values().stream()
.filter(v->v.size()>1)
.flatMap(列表::流)
.collect(toList());
(如果您愿意,可以在单个表达式中执行此操作。我只是将这些步骤分开,以便更清楚地了解正在发生的事情)


请注意,这不会保留原始列表中用户的顺序。

我在@holger的帮助下找到了解决方案:

// collect all duplicate names with O(n)
var duplicateNames = all.stream()
                .collect(Collectors.groupingBy(Strategy::getName, Collectors.counting()))
                .entrySet()
                .stream()
                .filter(m -> m.getValue() > 1)
                .map(m -> m.getKey())
                .collect(Collectors.toList());
此解决方案的性能是O(n^2)还是O(n)

如果有人能找到改进,请分享。

一个解决方案是

var duplicate = users.stream()
    .collect(Collectors.toMap(User::getName, u -> false, (x,y) -> true))
    .entrySet().stream()
    .filter(Map.Entry::getValue)
    .map(Map.Entry::getKey)
    .collect(Collectors.toSet());
这将创建一个中间
映射
,以记录哪个名称出现了多次。您可以使用该映射的
keySet()
,而不是收集到一个新的
集合

var duplicate = users.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.toMap(User::getName, u -> false, (x,y) -> true, HashMap::new),
            m -> {
                m.values().removeIf(dup -> !dup);
                return m.keySet();
            }));
循环解决方案可以简单得多:

HashSet<String> seen = new HashSet<>(), duplicate = new HashSet<>();
for(User u: users)
    if(!seen.add(u.getName())) duplicate.add(u.getName());
HashSet seen=newhashset(),duplicate=newhashset();
for(用户u:用户)
如果(!seen.add(u.getName()))重复.add(u.getName());

OP只需要名称,因此您不需要分组到
列表中,一个
分组依据(User::getName,counting())
,然后收集一个过滤后的
键集()
就足够了。Nit:您的原始代码中有一个集合,这里有一个列表。典型实现的复杂性是O(n),请参阅。您甚至可以简化计数,只保留一个
布尔值
,表示它是否重复,如中所示。@AndyTurner这有什么关系?
duplicateNames
只是一个包含重复名称(如果有)的
列表。它也可以是一个
集合
,但最终结果应该是一样的,因为我之前已经有一个带有唯一键的映射,由
收集器.groupingBy(Strategy::getName,Collectors.counting())
。此外,迭代方法只对集合传递一次。@Ravindranwala,流解决方案也只经过源集合一次。第二次迭代将在映射上进行,该映射可能已经包含比原始集合更少的元素,因为它只包含唯一的键。但是是的,这仍然比单循环的开销更大。我发现循环解决方案更容易阅读,代码更少。与streams解决方案相比,循环解决方案的开销更少。所以我坚持循环解决方案。谢谢是的,两次通过,一次通过源集合,另一次通过中间结果。迭代方法应优于流方法。它也更简洁易读。