我有一个数据结构,可以整理潜在的不区分大小写的命名冲突。
不区分大小写重复
将嵌套映射视为执行复合键的一种方式。
整数代表一类可能有重复的数据,
第一个字符串是字符串的大写版本
接下来的集合可以包含任意数量的版本.. .. 所以 JEREMY: ['Jeremy', 'jeremy','JEREMY'] 等是合理的数据。
目标是识别集合何时包含多个条目。数据的大写和小写版本可以共存,我必须识别这些情况。因此就有了这个数据结构。
有一个调用可以通过 Streams 过滤此内容:
(我的实际代码有一个枚举,其中 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);
}
}
}
}
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());
已修改此解决方案,感谢@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);
}
}
}
}