5

In java suppose I have 2 lists

List<Object1> list1
List<Object2> list2

object1.getName(); returns a String
object2.getName(); return a String

is there any way to compare the names and get a difference of the two list

those 2 objects are defined in the 3rd party library, and I can't override the equals and compareto methods

I am in favour of googles Guava or commons collections library

but the Sets.symmetricDifference(Set1, Set2) ask for 2 to be passed in, even i juse Sets.newHashSet(lis1) and Sets.newHashSet(lis2) to create two sets but still they have difference type of objects in the sets.

or in commons CollectionUtils.disjunction(lis1, list2) the lists still has to contain the same object type

without doing 2 expensive for loops, is there any other way?

Junchen Liu
  • 5,435
  • 10
  • 51
  • 62
  • How big are your lists? – Stefan Haberl Sep 10 '14 at 15:22
  • Why would the for loops be any more expensive than any other solution? Unless you're doing some really clever parallel execution, you're going to have to examine and transform every object exactly once. – Jeff Bowman Sep 10 '14 at 22:12
  • not really Jeff, try to compare 2 lists of objects, normally end up doing twice for loop with in another for loop. n*n + n*n. however create maps based on the those lists is n+n + n+n. I am asking this question because I wander is there any solution better than 4xns – Junchen Liu Sep 11 '14 at 08:59

3 Answers3

1

Using Guava, try this. It works for me ->

Multisets.difference(multiset1,multiset2);

How to convert ArrayList to Multiset.

 List x = new ArrayList();
 x.add(3);.....

 Multiset newX = HashMultiset.create();
 newX.addAll(x);
akshayb
  • 1,219
  • 2
  • 18
  • 44
1

First you will have to transfor your lists to String based lists:

private static final class FromObject1ToName implements Function<Object1, String> {
    @Override
    public String apply(Object1 input) {
        return input.name;
    }
}

The same transformation has to be done for Object2

Then transform the input list:

 Collection<String> transformed = Collections2.transform(list1, new FromObject1ToName());

//list1 is a List on Object1

Then create the multiset:

 Multiset<String> multiset1 = HashMultiset.create();
    multiset1.addAll(transformed);

Then simply do :

 Multisets.difference(multiset1, multiset2) // multiset1 is from Object1 and multiset2 is from Object2

This will give you the difference and how many times it differes

If you need to know just the differences, then do the same transform, then load the Collection of strings in a Set adn then do Sets.symmetricDifference

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • thanks, its cool, but once I got the difference which is a set of strings, I still have to for loop through each element in the lists to get the actual object1 or object2. I wander is there any better way doing this – Junchen Liu Sep 10 '14 at 14:50
  • @shanyangqu can your input list contain multiple names that are the same? I mean your input list that consists of Object1 instances can have two objects that have the same name? – Eugene Sep 10 '14 at 17:19
  • there can have same names on both list, but inside its own list, the names should be unique – Junchen Liu Sep 11 '14 at 08:54
1

First, we'll build two maps, one for each list, mapping names to objects. Then we iterate over the differences between the key sets, processing whichever kind of object had that name. The maps let us avoid scanning through the list looking for the object with that name. (In using Map rather than Multimap, I'm relying on the asker's comment on another answer that within each list, names are unique. If you're still using Java 7, replace the method reference with a Function implementation.)

Map<String, Object1> map1 = Maps.uniqueIndex(list1, Object1::getName);
Map<String, Object2> map2 = Maps.uniqueIndex(list2, Object1::getName);
for (String name : Sets.difference(map1.keySet(), map2.keySet()))
    processObject1(map1.get(name));
for (String name : Sets.difference(map2.keySet(), map1.keySet()))
    processObject2(map2.get(name));

If all you want to do is build lists or sets of the objects in exactly one list, processObject1 and processObject2 can just add the objects to collections.

uniqueIndex's iteration order is that of the input iterable, and difference returns a SetView with the same iteration order as its first argument, so you can process objects in the order they appeared in the input lists, if that order is relevant to your problem.


Java 8 streams provide basically the same functionality:

Map<String, Object1> map1 = list1.stream().collect(Collectors.toMap(Function.identity(), Object1::getName));
Map<String, Object2> map2 = list2.stream().collect(Collectors.toMap(Function.identity(), Object2::getName));
map1.keySet().stream().filter(n -> !map2.keySet().contains(n)).map(map1::get).forEachOrdered(o1 -> processObject1(o1));
map2.keySet().stream().filter(n -> !map1.keySet().contains(n)).map(map2::get).forEachOrdered(o2 -> processObject1(o2));

Again, you can replace the forEachOrdered call with collect(Collectors.toList()) if you just want to collect the objects.

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92