比较相同内容的可迭代对象,但不考虑顺序

时间:2019-06-11 22:53:19

标签: java collections iterator

我正在尝试比较相同大小的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,而且不能正确解释重复项。

3 个答案:

答案 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 answerthis 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);
   }