简体   繁体   English

比较两个列表并获得差异

[英]Comparing two lists and getting differences

I have two lists. 我有两个清单。 They contain objects of different types, but both types contain id and name, and id is what I am comparing on. 它们包含不同类型的对象,但这两种类型都包含id和name,而id是我要比较的。 List one is fetched from DB, and list two is sent from frontend. 列表一从DB中获取,列表二从前端发送。

What I need to do is loop through them and find which list item is newly added and which one was deleted. 我需要做的是循环它们,找到新添加的列表项和删除的列表项。

I was able to do it, but the problem is that it look ugly. 我能够做到,但问题是它看起来很难看。

Let's say I have a object that is called NameDTO which can have id and name. 假设我有一个名为NameDTO的对象,它可以有id和name。 List two is filled with that type of objects. 列表2填充了该类型的对象。

This is how I did it: 这就是我做的方式:

final ArrayList<NamedDTO> added = new ArrayList<>();
final ArrayList<NamedDTO> removed = new ArrayList<>();

for(NamedDTO listTwoObject : listTwo) {
   boolean contained = false;
   for(SomeObject listOneObject : listOne) {
       if(listTwoObject.getId().equals(listOneObject.getId()) {
           contained = true;
       }
   }
   if(!contained) {
      added.add(listTwoObject);
   }
}

for(SomeObject listOneObject : listOne) {
   boolean contained = false;
   for(NamedDTO listTwoObject : listTwo) {
       if(listTwoObject.getId().equals(listOneObject.getId()) {
           contained = true;
       }
   }
   if(!contained) {
      removed.add(new NamedDTO(listOneObject.getId(), listOneObject.getName()));
  }
}

This works, I have tested it. 这有效,我已经测试过了。 Are there better solutions? 有更好的解决方案吗? I was thinking of using Sets so I can compare them, Is there a downside to that ? 我正在考虑使用套装,所以我可以比较它们,这有什么缺点吗?

If I understand correctly, this is the example scenario: 如果我理解正确,这是示例场景:

  • listOne [datab] items: [A, B, C, D] listOne [datab]项目: [A, B, C, D]
  • listTwo [front] items: [B, C, D, E, F] listTwo [front]项目: [B, C, D, E, F]

and what you need to get as an effect is: 你需要得到的效果是:

  • added: [E, F] 补充: [E, F]
  • deleted: [A] 删除: [A]

First thing first, I would use some type adapter or extend the different types from one common class and override the equals method so you can match them by id and name 首先,我会使用某种类型的适配器或从一个公共类扩展不同的类型并override equals方法,以便您可以通过idname匹配它们

Secondly, this is very easy operations on sets (you could use set's but list are fine too). 其次,这是非常简单的集合操作(​​你可以使用set,但列表也很好)。 I recommend using a library: https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/CollectionUtils.html 我建议使用库: https//commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/CollectionUtils.html

And now basically: 现在基本上:

  • added is listTwo - listOne 添加的是listTwo - listOne
  • deleted is listOne - listTwo 删除是listOne - listTwo

and using java code: 并使用java代码:

  • added: CollectionUtils.removeAll(listTwo, listOne) 补充: CollectionUtils.removeAll(listTwo, listOne)
  • deleted: CollectionUtils.removeAll(listOne, listTwo) 已删除: CollectionUtils.removeAll(listOne, listTwo)

Otherwise, all collections implementing Collection ( Java Docs ) also has removeAll method, which you can use. 否则,实现CollectionJava Docs )的所有集合也都有removeAll方法,您可以使用它。

I propose solution using java 8 streams: 我建议使用java 8流解决方案:

    ArrayList<ObjOne> list = new ArrayList<>(Arrays.asList(new ObjOne("1","1"),new ObjOne("3","3"),new ObjOne("2","2")));
    ArrayList<ObjTwo> list2 = new ArrayList<>(Arrays.asList(new ObjTwo("1","1"),new ObjTwo("3","3"),new ObjTwo("4","4")));

    List<ObjOne> removed = list.stream().filter(o1 -> list2.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
            .collect(Collectors.toList());
    System.out.print("added ");
    removed.forEach(System.out::println);

    List<ObjTwo> added = list2.stream().filter(o1 -> list.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
             .collect(Collectors.toList());

    System.out.print("removed ");
    added.forEach(System.out::println);

This is basically your solution but implemented using streams, which will make your code shorter and easer to read 这基本上是您的解决方案,但使用流实现,这将使您的代码更短,更易于阅读

This nested list processing is not only ugly, it's inefficient. 这种嵌套列表处理不仅难看,而且效率低下。 You are always better off storing the IDs of one list into a Set allowing efficient lookup, then process the other list utilizing the Set . 最好将一个列表的ID存储到允许高效查找的Set ,然后使用Set处理另一个列表。 This way, you're not performing list1.size() times list2.size() operations, but list1.size() plus list2.size() operations, which is a significant difference for larger lists. 这样,您不会执行list1.size()list2.size()操作,而是执行list1.size()list2.size()操作,这对于较大的列表来说是一个显着的区别。 Then, since both operations are fundamentally the same, it's worth abstracting them into a method: 然后,由于两个操作基本相同,因此值得将它们抽象为一个方法:

public static <A,B,R,ID> List<R> extract(
    List<A> l1, List<B> l2, Function<A,ID> aID, Function<B,ID> bID, Function<A,R> r) {

    Set<ID> b=l2.stream().map(bID).collect(Collectors.toSet());
    return l1.stream().filter(a -> !b.contains(aID.apply(a)))
             .map(r).collect(Collectors.toList());
}

This method can be used as 这种方法可以用作

List<NamedDTO> added   = extract(listTwo, listOne, NamedDTO::getId, SomeObject::getId,
                                 Function.identity());
List<NamedDTO> removed = extract(listOne, listTwo, SomeObject::getId, NamedDTO::getId,
                                 so -> new NamedDTO(so.getId(), so.getName()));

Since swapping the two lists requires the helper method to be independent from the element types, it expects functions for accessing the id property, which can be specified via method references. 由于交换两个列表需要辅助方法独立于元素类型,因此它需要访问id属性的函数,可以通过方法引用指定。 Then, a function describing the result element is required, which is an identity function in one case (just getting the NamedDTO ) and a lambda expression constructing a NamedDTO from SomeObject in the other. 然后,需要一个描述结果元素的函数,它是一种情况下的一个标识函数(只是获取NamedDTO )和另一种情况下从SomeObject构造一个NamedDTO的lambda表达式。

The operation itself is as straight-forward as described above, iterate over one list, map to the id and collect into a Set , then, iterate over the other list, keep only elements whose id is not in the set, map to the result type and collect into a List . 操作本身如上所述一样直接,迭代一个列表,映射到id并收集到一个Set ,然后迭代另一个列表,只保留id不在集合中的元素,映射到结果输入并收集到List

If these id's are unique, you could put them in a HashSet and find thus the ids you are interested in: 如果这些id是唯一的,你可以把它们放在一个HashSet中,找到你感兴趣的id:

    Set<Integer> uiList = Stream.of(new FromUI(1, "db-one"), new FromUI(2, "db-two"), new FromUI(3, "db-three"))
            .map(FromUI::getId)
            .collect(Collectors.toCollection(HashSet::new));
    Set<Integer> dbList = Stream.of(new FromDB(3, "ui-one"), new FromDB(5, "ui-five"))
            .map(FromDB::getId)
            .collect(Collectors.toCollection(HashSet::new));

    uiList.removeIf(dbList::remove);

added/uiSet :   [1,2]
removed/dbSet : [5]

I've created FromUI and FromDB classes with a constructor that takes the id and name as input. 我已经使用构造函数创建了FromUIFromDB类,该构造函数将id和name作为输入。

I also assume that if an element is contained in the uiSet , but not in dbSet is has been added and the other way around. 我还假设如果一个元素包含在uiSet ,但是在dbSet中没有添加, dbSet

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM