Java Collectors Streaming 过滤器和 .toMap 检测各种类型的不区分大小写的重复项时

问题描述 投票:0回答:2

我有一个数据结构,可以整理潜在的不区分大小写的命名冲突。

  • 不区分大小写重复

  • 将嵌套映射视为执行复合键的一种方式。

  • 整数代表一类可能有重复的数据,

  • 第一个字符串是字符串的大写版本

  • 接下来的集合可以包含任意数量的版本.. .. 所以 JEREMY: ['Jeremy', 'jeremy','JEREMY'] 等是合理的数据。

目标是识别集合何时包含多个条目。数据的大写和小写版本可以共存,我必须识别这些情况。因此就有了这个数据结构。

  • 因此数据 N1 和 n1 将是在大写 N1 上键入的两个条目,我希望将其返回到结果中。

有一个调用可以通过 Streams 过滤此内容:

  • 我必须在 EntrySet 上工作以将键/值保持在一起。我就知道这么多了。
  • 我想返回与开始时相同的数据结构(caseInsensitiveDuplicates 的类型)
  • 我知道我需要过滤超过 1 的尺寸。

(我的实际代码有一个枚举,其中 Integer 所在,以及一个自定义类,其中 String 位于声明它的行上的 Set 内。请参阅下面的代码)。

从初始数据来看:

1 : N1 : [n1, N1]
1 : N2 : [n2]

我的预期结果将是一个包含如下数据的数据结构:

1 : N1 : [n1, N1]

这是代码的可运行版本的链接

我在流的

filter()

部分的最初尝试是使用:

e -> e.getValue().values().size() > 1
这只会返回所有内容

caseInsensitiveDuplicates.keySet().size(): 1 t1Dups.keySet().size(): 2 k: N1 v: N1 v: n1 k: N2 v: n2 N1 size: 2 N2 size: 1 --- k:N1 v:N1 v:n1 k:N2 v:n2
@Eritrean 表示最新修改

e -> e.getValue().values().stream().allMatch(set -> set.size() > 1)
以及 

Collectors.toMap()

 的语法糖

感谢这部分。

尝试调试后,代码当前打印:

caseInsensitiveDuplicates.keySet().size(): 1 t1Dups.keySet().size(): 2 k: N1 v: N1 v: n1 k: N2 v: n2 N1 size: 2 N2 size: 1 dupsAllTypes keyset was empty


我在

这里发布了一个强力非流式解决方案。但仍然想看看是否可以通过流/过滤器以更有效的方式解决这个问题。


import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.stream.Collectors; import static java.lang.System.out; public class TestDupNameDataStructureFilter { public static void main(String[] args) { Map<Integer, Map<String, Set<String>>> caseInsensitiveDuplicates = new HashMap<>(); Set<String> n1Set = new TreeSet<>(); n1Set.add("n1"); n1Set.add("N1"); Set<String> n2Set = new TreeSet<>(); n2Set.add("n2"); Map<String, Set<String>> m1 = new TreeMap<>(); m1.put("N1", n1Set); Map<String, Set<String>> m2 = new TreeMap<>(); m2.put("N2", n2Set); Integer nmt = 1; caseInsensitiveDuplicates.put(nmt, m1); Map<String, Set<String>> temp = caseInsensitiveDuplicates.get(nmt); temp.put("N2", n2Set); caseInsensitiveDuplicates.put(nmt, temp); out.println("caseInsensitiveDuplicates.keySet().size(): " + caseInsensitiveDuplicates.keySet().size()); Map<String, Set<String>> t1Dups = caseInsensitiveDuplicates.get(nmt); out.println("t1Dups.keySet().size(): " + t1Dups.keySet().size()); for (String k : t1Dups.keySet()) { out.println("k: " + k); for (String v : t1Dups.get(k)) { out.println("v: " + v); } } out.println("N1 size: " + caseInsensitiveDuplicates.get(nmt).get("N1").size()); out.println("N2 size: " + caseInsensitiveDuplicates.get(nmt).get("N2").size()); Map<Integer, Map<String, Set<String>>> dupsAllTypes = caseInsensitiveDuplicates .entrySet() .stream() //.filter( e -> e.getValue().values().size() > 1) .filter( e -> e.getValue().values().stream().allMatch(set -> set.size() > 1) ) .collect( Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue) ); if (dupsAllTypes == null) { out.println("dupsAllTypes was null"); return; } else if (dupsAllTypes.keySet().size() == 0) { out.println("dupsAllTypes keyset was empty"); return; } Map<String, Set<String>> dups = dupsAllTypes.get(nmt); if (dups == null) { out.println("dups was null"); return; } else if (dups.keySet().size() == 0) { out.println("dups keyset was empty"); return; } out.println("---"); for (String k : dups.keySet()) { out.println("k:" + k); for (String v : dups.get(k) ) { out.println("v:" + v); } } } }
    
java java-stream
2个回答
2
投票
Collection API 解决方案可以简单地表示为

Map<Integer, Map<String, Set<String>>> dupsAllTypes = new HashMap<>(); caseInsensitiveDuplicates.forEach((k1,kv) -> kv.forEach((k2,v) -> { if(v.size() > 1) dupsAllTypes.computeIfAbsent(k1, key -> new TreeMap<>()).put(k2, v); }) );
等效的 Stream API 解决方案是

Map<Integer, Map<String, Set<String>>> dupsAllTypes = caseInsensitiveDuplicates.entrySet().stream() .filter(e -> e.getValue().values().stream().anyMatch(s -> s.size() > 1)) .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream() .filter(e2 -> e2.getValue().size() > 1) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (a,b) -> { throw new AssertionError(); }, TreeMap::new))) );
这由于缺少一对 Stream 的 API,所以我们必须使用 

Stream

Map.Entry
 实例并插入 
getKey
getValue
 调用,这不像 
Map
 那样简洁
forEach
方法。

此外,您想要内部地图的

TreeMap

 结果,但没有 
Collector
 接受没有合并功能的地图工厂。因此,尽管我们知道不能有重复的键,但我们必须提供合并函数,因为源已经是一个映射。因此,这个解决方案提供了
(a,b) -> { throw new AssertionError(); }
合并功能。

此方法需要两次

filter

 操作。我们可以通过先进行内部映射处理来减少工作量,但这需要临时存储:

Map<Integer, Map<String, Set<String>>> dupsAllTypes = caseInsensitiveDuplicates.entrySet().stream() .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), e.getValue().entrySet().stream() .filter(e2 -> e2.getValue().size() > 1) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (a,b) -> { throw new AssertionError(); }, TreeMap::new)))) .filter(e -> !e.getValue().isEmpty()) .collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
在这里,第二个过滤器是一个微不足道的操作。

请注意,对于您的任务,Stream API 解决方案不仅更复杂,而且不提供任何性能优势。基本上,它们的作用与 Collection API 方法相同。由于所描述的 API 限制,它们的作用甚至更多。它们可以并行运行,但您需要非常大的数据集才能从并行处理中获益。


如果原始数据存储在可变集合中,并且之后不需要原始状态,换句话说,当允许修改原始集合时,可以就地操作:

caseInsensitiveDuplicates.values() .removeIf(map -> map.values().removeIf(set -> set.size() <= 1) && map.isEmpty());
这将删除所有不具有至少两个元素的集合,如果结果留下空的内部映射,则该内部映射也会被删除。

作为一个极端情况,如果内部地图一开始是空的,它不会删除它。如果这些地图也将被删除,你必须使用类似的东西

caseInsensitiveDuplicates.values() .removeIf(map -> map.isEmpty() || map.values().removeIf(set -> set.size() <= 1) && map.isEmpty());
    

0
投票
如果不手动构建它,我无法弄清楚如何做到这一点。如果其他人提出了使用 Streams 执行此操作的正确方法,我会接受该答案。

已修改此解决方案,感谢@Holger 提供以下提示:

  • entrySet().forEach(…)
     -> 功能风格:
    forEach((k,v) -> ...)
    
    
  • 如果不存在则计算
  • map.keySet().size() == 0
     -> 删节 
    map.isEmpty()
    
    

import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; import static java.lang.System.out; public class TestDupNameDataStructureFilter { public static void main(String[] args) { // Core data structure Map<Integer, Map<String, Set<String>>> caseInsensitiveDuplicates = new HashMap<>(); // Setup test data Set<String> n1Set = new TreeSet<>(); n1Set.add("n1"); n1Set.add("N1"); Set<String> n2Set = new TreeSet<>(); n2Set.add("n2"); Map<String, Set<String>> m1 = new TreeMap<>(); m1.put("N1", n1Set); Map<String, Set<String>> m2 = new TreeMap<>(); m2.put("N2", n2Set); Integer nmt = 1; caseInsensitiveDuplicates.put(nmt, m1); Map<String, Set<String>> temp = caseInsensitiveDuplicates.get(nmt); temp.put("N2", n2Set); caseInsensitiveDuplicates.put(nmt, temp); // Sanity check data structure has intended data with some output out.println("caseInsensitiveDuplicates.keySet().size(): " + caseInsensitiveDuplicates.keySet().size()); Map<String, Set<String>> t1Dups = caseInsensitiveDuplicates.get(nmt); out.println("t1Dups.keySet().size(): " + t1Dups.keySet().size()); for (String k : t1Dups.keySet()) { out.println("k: " + k); for (String v : t1Dups.get(k)) { out.println("v: " + v); } } out.println("N1 size: " + caseInsensitiveDuplicates.get(nmt).get("N1").size()); out.println("N2 size: " + caseInsensitiveDuplicates.get(nmt).get("N2").size()); // Core of solution Map<Integer, Map<String, Set<String>>> dupsAllTypes = new HashMap<>(); caseInsensitiveDuplicates.forEach( (k1,kv) -> { kv.forEach((k2,v) -> { if (v.size() > 1) { Map<String, Set<String>> temp2 = dupsAllTypes.computeIfAbsent(k1, key -> new TreeMap<>()); temp2.put(k2, v); dupsAllTypes.put(k1, temp2); } }); }) // Debugging code if (dupsAllTypes.isEmpty()) { out.println("dupsAllTypes keyset was empty"); return; } Map<String, Set<String>> dups = dupsAllTypes.get(nmt); if (dups == null) { out.println("dups was null"); return; } else if (dups.isEmpty()) { out.println("dups keyset was empty"); return; } out.println("---"); for (String k : dups.keySet()) { out.println("k:" + k); for (String v : dups.get(k) ) { out.println("v:" + v); } } } }
    
© www.soinside.com 2019 - 2024. All rights reserved.