我正在尝试比较相同大小的Java中的两个Iterable。我只需要知道内容是一样的。但是,诸如[1、2]和[1、2、2]之类的东西不应该相等,而[1、2、2、4]之类的应该等于[1、2、4、2]。
boolean functionName() {
boolean pvk;
... setup ...
for(Edge e : pMST.edges()) {
pvk = false;
for(Edge f : kMST.edges()) {
if(e == f) {
pvk = true;
System.out.println("True.");
}
}
if(!pvk) return false;
}
return true;
}
这是我最初的糟糕尝试,但不仅总是返回false,而且不能正确解释重复项。
答案 0 :(得分:2)
您可以对项目进行排序并比较结果列表,但这可能会降低O(n lg n)的效率,它依赖于项目是可比较的,还是取决于项目的总顺序比较器。这可能是不可行的。
此other answer建议使用番石榴多集。这很有意义,因为它可以跟踪元素和出现次数,这对您的问题很重要。对于合理的实现,例如HashMultiset,它应该为O(n)。其他库,例如Apache Commons(MultiSet)和Eclipse Collections(Bag)的集合实现在功能上等效于Guava的Multiset。
如果您不想包括对任何这些库的依赖,则可以在JDK中自己完成。不幸的是Java没有实现 Bag 的实现,但是为此目的,很容易使用Map从您的项目类型到Integer或Long计数来模拟它。
如果您有列表,则可以执行以下操作:
boolean unorderedEquals(List<Item> list1, List<Item> list2) {
Map<Item, Long> freq1 = list1.stream().collect(groupingBy(i -> i, counting()));
Map<Item, Long> freq2 = list2.stream().collect(groupingBy(i -> i, counting()));
return freq1.equals(freq2);
}
如果您有Iterables,则需要使用forEach
来构建地图:
boolean unorderedEquals(Iterable<Item> iter1, Iterable<Item> iter2) {
Map<Item, Integer> freq1 = new HashMap<>();
iter1.forEach(it -> freq1.merge(it, 1, (a, b) -> a + b));
Map<Item, Integer> freq2 = new HashMap<>();
iter2.forEach(it -> freq2.merge(it, 1, (a, b) -> a + b));
return freq1.equals(freq2);
}
答案 1 :(得分:2)
将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
,而无需进一步检查。
支持任意Iterable
,这与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();
}
当计数达到零时,它会从地图中删除条目,因此我们只需要最后检查地图的空度即可。
答案 2 :(得分:1)
我将对它们进行排序。但是首先,我将在进行排序之前比较大小。您需要提供一个Comparator<T>
以便供sort方法使用。如果要对整数进行排序,则可以使用:
List<Integer> a = new ArrayList<>(List.of(1, 2, 3, 3, 3, 3, 4, 5, 6));
List<Integer> b = new ArrayList<>(List.of(2, 3, 1, 3, 4, 5, 6, 3, 3));
System.out.println(compareLists(a, b, Comparator.naturalOrder()));
public static <T> boolean compareList(List<T> list1, List<T> list2,
Comparator<T> comp) {
if (list1 == list2) {
return true;
}
if (list1.size() != list2.size()) {
return false;
}
Collections.sort(list1, comp);
Collections.sort(list2, comp);
return list1.equals(list2);
}