结合this answer 与this thread 的想法,特别是this answer 来创建一个高效但可读的解决方案,您可以使用
static boolean unorderedEquals(Collection<?> coll1, Collection<?> coll2) {
if(coll1.size() != coll2.size()) return false;
Map<Object, Integer> freq = new HashMap<>();
for(Object o: coll1) freq.merge(o, 1, Integer::sum);
for(Object o: coll2)
if(freq.merge(o, -1, Integer::sum) < 0) return false;
return true;
}
第一个循环像链接的答案一样创建一个频率图,但不是构建第二个图来执行昂贵的比较,而是第二个循环减少每次出现的计数,如果计数变为负数,则立即返回。 merge 方法可以顺利处理缺少键的情况。
由于在方法的开头已经检查了两个列表的大小相同,因此在增加和减少之后,总计数必须为零。由于我们已经证明不存在负数,因此我们立即为它们返回,因此也不可能存在正的非零值。所以我们可以在第二次循环之后返回true,而无需进一步检查。
支持任意的Iterables,它与Collection 的不同之处在于不一定有size() 方法,有点棘手,因为我们不能进行预检查,因此必须保持计数:
static boolean unorderedEquals(Iterable<?> iter1, Iterable<?> iter2) {
Map<Object, Integer> freq = new HashMap<>();
int size = 0;
for(Object o: iter1) {
freq.merge(o, 1, Integer::sum);
size++;
}
for(Object o: iter2)
if(--size < 0 || freq.merge(o, -1, Integer::sum) < 0) return false;
return size == 0;
}
如果我们想避免装箱开销,我们必须为地图使用可变值,例如
static boolean unorderedEquals(Collection<?> coll1, Collection<?> coll2) {
if(coll1.size() != coll2.size()) return false;
Map<Object, int[]> freq = new HashMap<>();
for(Object o: coll1) freq.computeIfAbsent(o, x -> new int[1])[0]++;
int[] absent = { 0 };
for(Object o: coll2) if(freq.getOrDefault(o, absent)[0]-- == 0) return false;
return true;
}
但我认为他不会有回报。对于少量出现,装箱将重用 Integer 实例,而在使用可变值时,我们需要为每个不同元素使用不同的 int[] 对象。
但是使用compute 可能对Iterable 解决方案很有趣,像这样使用它时
static boolean unorderedEquals(Iterable<?> coll1, Iterable<?> coll2) {
Map<Object, int[]> freq = new HashMap<>();
for(Object o: coll1) freq.computeIfAbsent(o, x -> new int[1])[0]++;
int[] absent = {};
for(Object o: coll2)
if(freq.compute(o, (key,c) -> c == null || c[0] == 0? absent:
--c[0] == 0? null: c) == absent) return false;
return freq.isEmpty();
}
当条目计数为零时从映射中删除条目,因此我们只需要在最后检查映射是否为空。