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. The list is selected from the database, and list two is sent from the interface.

What I need to do is look through them and find which list item has been recently added and which one has been deleted.

I was able to do this, but the problem is that it looks ugly.

Let's say I have an object called NameDTO, which can have an identifier and a name. The list of two is filled with such types of objects.

Here 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())); } } 

It works, I tested it. Are there any better solutions? I was thinking about using Sets so that I can compare them. Is there a flaw in this?

+8
java spring-boot java-8
source share
4 answers

If I understand correctly, this is an example script:

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

and what you need to get as an effect:

  • added by: [E, F]
  • deleted: [A]

First of all, I would use some type of adapter or extend different types from one common class and override the equals method so that you can map them using id and name

Secondly, these are very simple operations on sets (you can use set, but the list is good too). I recommend using the library: https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/CollectionUtils.html

And now basically:

  • listTwo - listOne added listTwo - listOne
  • deleted listOne - listTwo

and using Java code:

  • added: CollectionUtils.removeAll(listTwo, listOne)
  • deleted: CollectionUtils.removeAll(listOne, listTwo)

Otherwise, all collections that implement Collection ( Java Docs ) also have a removeAll method that you can use.

+8
source share

This nested list processing is not only ugly, but also inefficient. You are always better off storing the identifiers of one list in Set , allowing an efficient search, and then process another list using Set . Thus, you do not perform the operations list1.size() times list2.size() , but list1.size() plus list2.size() , which is a significant difference for larger lists. Then, since both operations are essentially the same, it is 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 replacing two lists requires the helper method to be independent of element types, it expects a function to access the id property, which can be set using method references. Then you need a function that describes the result element, which is an identical function in one case (just getting NamedDTO ) and a lambda expression that NamedDTO from SomeObject in another.

The operation itself is performed in the same straightforward manner as described above, iterating over one list, matching with the identifier and collecting in Set , and then iterating over the other list, saving only the elements whose identifier is not in the set, compare the result type and take it to List .

+4
source share

I suggest a solution using java 8 threads:

  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 threads, which will make your code shorter and easier to read.

+4
source share

If these identifiers are unique, you can put them in a HashSet and find, in this way, the identifiers that interest you:

  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 created the FromUI and FromDB with a constructor that takes an identifier and a name as input.

I also assume that if an element is contained in a uiSet , but not in dbSet was added, but vice versa.

+2
source share

All Articles