41

我有两个相同对象的集合,Collection<Foo> oldSet并且Collection<Foo> newSet. 所需逻辑如下:

  • 如果foo在 (*)oldSet但不是newSet,则调用doRemove(foo)
  • else if foois not in oldSetbut in newSet, 调用doAdd(foo)
  • 否则,如果foo在两个集合中但已修改,则调用doUpdate(oldFoo, newFoo)
  • 否则!foo.activated && foo.startDate >= now,调用doStart(foo)
  • 否则foo.activated && foo.endDate <= now,调用doEnd(foo)

(*) “in”表示唯一标识符匹配,不一定是内容。

当前(旧版)代码进行许多比较以找出removeSetaddSet、和updateSet,然后循环对每个项目进行操作。startSetendSet

代码非常混乱(部分原因是我已经遗漏了一些意大利面条逻辑),我正在尝试重构它。更多背景信息:

  • 据我所知,oldSetandnewSet实际上是由ArrayList
  • 每组包含少于 100 个项目,最有可能最多 20 个
  • 此代码被频繁调用(以百万/天为单位),尽管集合很少不同

我的问题:

  • 如果我转换oldSet并转换newSetHashMap<Foo>(这里不关心顺序),以 ID 作为键,是否会使代码更易于阅读和比较?转换会损失多少时间和内存性能?
  • 迭代这两组并执行适当的操作会更有效和简洁吗?
4

8 回答 8

36

Apache 的 commons.collections 库有一个 CollectionUtils 类,该类为 Collection 操作/检查提供易于使用的方法,例如交集、差异和联合。

org.apache.commons.collections.CollectionUtils API 文档在这里

于 2009-07-22T18:29:03.213 回答
22

例如,您可以使用 Java 8 流

set1.stream().filter(s -> set2.contains(s)).collect(Collectors.toSet());

或 从Guava设置类:

Set<String> intersection = Sets.intersection(set1, set2);
Set<String> difference = Sets.difference(set1, set2);
Set<String> symmetricDifference = Sets.symmetricDifference(set1, set2);
Set<String> union = Sets.union(set1, set2);
于 2011-12-26T18:21:34.170 回答
11

我已经创建了一个我认为您正在寻找的近似值,只是使用 Java 中的 Collections Framework。坦率地说,我认为正如@Mike Deck 指出的那样,这可能是矫枉过正。对于这样一小部分要比较和处理的项目,我认为从程序的角度来看,数组会是一个更好的选择,但这是我的伪编码(因为我很懒)解决方案。我假设 Foo 类基于它的唯一 id 而不是其内容中的所有数据是可比较的:

Collection<Foo> oldSet = ...;
Collection<Foo> newSet = ...;

private Collection difference(Collection a, Collection b) {
    Collection result = a.clone();
    result.removeAll(b)
    return result;
}

private Collection intersection(Collection a, Collection b) {
    Collection result = a.clone();
    result.retainAll(b)
    return result;
}

public doWork() {
    // if foo is in(*) oldSet but not newSet, call doRemove(foo)
    Collection removed = difference(oldSet, newSet);
    if (!removed.isEmpty()) {
        loop removed {
            Foo foo = removedIter.next();
            doRemove(foo);
        }
    }
    //else if foo is not in oldSet but in newSet, call doAdd(foo)
    Collection added = difference(newSet, oldSet);
    if (!added.isEmpty()) {
        loop added  {
            Foo foo = addedIter.next();
            doAdd(foo);
        }
    }

    // else if foo is in both collections but modified, call doUpdate(oldFoo, newFoo)
    Collection matched = intersection(oldSet, newSet);
    Comparator comp = new Comparator() {
        int compare(Object o1, Object o2) {
            Foo f1, f2;
            if (o1 instanceof Foo) f1 = (Foo)o1;
            if (o2 instanceof Foo) f2 = (Foo)o2;
            return f1.activated == f2.activated ? f1.startdate.compareTo(f2.startdate) == 0 ? ... : f1.startdate.compareTo(f2.startdate) : f1.activated ? 1 : 0;
        }

        boolean equals(Object o) {
             // equal to this Comparator..not used
        }
    }
    loop matched {
        Foo foo = matchedIter.next();
        Foo oldFoo = oldSet.get(foo);
        Foo newFoo = newSet.get(foo);
        if (comp.compareTo(oldFoo, newFoo ) != 0) {
            doUpdate(oldFoo, newFoo);
        } else {
            //else if !foo.activated && foo.startDate >= now, call doStart(foo)
            if (!foo.activated && foo.startDate >= now) doStart(foo);

            // else if foo.activated && foo.endDate <= now, call doEnd(foo)
            if (foo.activated && foo.endDate <= now) doEnd(foo);
        }
    }
}

至于您的问题:如果我将 oldSet 和 newSet 转换为 HashMap (这里不关心顺序),以 ID 作为键,它是否会使代码更易于阅读和比较?转换会损失多少时间和内存性能?我认为您可能会通过使用 Map BUT 使代码更具可读性......您可能会在转换过程中使用更多的内存和时间。

迭代这两组并执行适当的操作会更有效和简洁吗?是的,这将是两全其美的方法,特别是如果您遵循@Mike Sharek 的建议,即使用专门的方法滚动您自己的列表,或者遵循类似访客设计模式的方法来运行您的收藏并处理每个项目。

于 2008-08-23T03:54:59.143 回答
4

我认为最简单的方法是使用 apache collections api - CollectionUtils.subtract(list1,list2) 只要​​列表属于同一类型。

于 2010-06-23T18:29:09.260 回答
2

我会移动到列表并以这种方式解决它:

  1. 如果列表中的对象不可比较,则使用自定义比较器按 id 升序对两个列表进行排序
  2. 迭代两个列表中的元素,例如合并排序算法中的合并阶段,但不是合并列表,而是检查逻辑。

代码或多或少是这样的:

/* Main method */
private void execute(Collection<Foo> oldSet, Collection<Foo> newSet) {
  List<Foo> oldList = asSortedList(oldSet);
  List<Foo> newList = asSortedList(newSet);

  int oldIndex = 0;
  int newIndex = 0;
  // Iterate over both collections but not always in the same pace
  while( oldIndex < oldList.size() 
      && newIndex < newIndex.size())  {
    Foo oldObject = oldList.get(oldIndex);
    Foo newObject = newList.get(newIndex);

    // Your logic here
    if(oldObject.getId() < newObject.getId()) {
      doRemove(oldObject);
      oldIndex++;
    } else if( oldObject.getId() > newObject.getId() ) {
      doAdd(newObject);
      newIndex++;
    } else if( oldObject.getId() == newObject.getId() 
            && isModified(oldObject, newObject) ) {
      doUpdate(oldObject, newObject);
      oldIndex++;
      newIndex++;
    } else {
      ... 
    }
  }// while

  // Check if there are any objects left in *oldList* or *newList*

  for(; oldIndex < oldList.size(); oldIndex++ ) {
    doRemove( oldList.get(oldIndex) );  
  }// for( oldIndex )

  for(; newIndex < newList.size(); newIndex++ ) {
    doAdd( newList.get(newIndex) );
  }// for( newIndex ) 
}// execute( oldSet, newSet )

/** Create sorted list from collection 
    If you actually perform any actions on input collections than you should 
    always return new instance of list to keep algorithm simple.
*/
private List<Foo> asSortedList(Collection<Foo> data) {
  List<Foo> resultList;
  if(data instanceof List) {
     resultList = (List<Foo>)data;
  } else {
     resultList = new ArrayList<Foo>(data);
  }
  Collections.sort(resultList)
  return resultList;
}
于 2008-08-31T17:58:00.300 回答
0
public static boolean doCollectionsContainSameElements(
        Collection<Integer> c1, Collection<Integer> c2){

    if (c1 == null || c2 == null) {
        return false;
    }
    else if (c1.size() != c2.size()) {
        return false;
    } else {    
        return c1.containsAll(c2) && c2.containsAll(c1);
    }       
}
于 2016-02-09T15:44:38.070 回答
-1

对于一个很小的集合,通常不值得从 Array 转换为 HashMap/set。事实上,您最好将它们保存在一个数组中,然后通过键对它们进行排序并同时遍历两个列表以进行比较。

于 2008-08-22T20:57:38.663 回答
-2

为了比较列表或集合,我们可以使用Arrays.equals(object[], object[]). 它将仅检查值。要获得Object[]我们可以使用的Collection.toArray()方法。

于 2010-12-17T10:05:08.330 回答