Hot questions for Comparing the Data in Guava

Top 10 Java Open Source / Guava / Comparing the Data

Guava: How to create an explicit Ordering from a List and a single element?

Question: In Guava, given a Collection<E> and an element e of type E that I know is in the collection, I'd like to create a custom Ordering<E> that sorts e first and then the rest of the collection. However, the way to get there seems awfully complicated:

Collection<String> values = ImmutableList.of("apples", "oranges", "pears");
String first = "oranges";

List<String> remainingValues = newArrayList(values);  // this
remainingValues.remove(first);                        // seems
Ordering<String> myOrdering =                         // very
    Ordering.explicit(first, remainingValues.toArray( // complicated!
        new String[remainingValues.size()]));         // is there an easier way?

What I'm wishing for is either something like this:

Ordering.explicit(first);

I'd like this to sort first to the beginning and retain the order of all other elements, but the docs say the resulting Ordering will throw a ClassCastException for elements not explicitly listed.

Or like this:

Ordering.explicit(first, values.toArray(/* etc */));

But this would fail because first would be a duplicate value

Can anybody come up with a concise way of doing what I want?

BTW, it doesn't have to be an Ordering, it could also be a workaround for creating an Iterable in the specified Order, but again, this is very complicated:

Iterable<String> sorted = Iterables.concat(
                             ImmutableList.of(first),
                             Iterables.filter(values, not(equalTo(first))));

Answer: Well, here's one way to do it, but you may not find it much better.

final String special = "oranges";
Collections.sort(
    list,
    new Comparator<String>() {
      public int compare(String left, String right) {
        return ComparisonChain.start()
            .compareTrueFirst(left.equals(special), right.equals(special))
            .compare(left, right)
            .result();
      }
    });

Compare sets inside a set

Question: I have a set like this :

Set<Set<Node>> NestedSet = new HashSet<Set<Node>>();

[[Node[0], Node[1], Node[2]], [Node[0], Node[2], Node[6]], [Node[3], Node[4], Node[5]]]

I want to compare and merge sets that are inside the nested set. [0,1,2] and [0,2,6] has element in common. so should merge them to form 0,1,2,6.

The output should be like this:

[[Node[0], Node[1], Node[2], Node[6]], [Node[3], Node[4], Node[5]]]

Is there any efficient way?

Answer: ou can use Collections.disjoint(Collection c1, Collection c2) to check two specified collections have no elements in common.

Btw make sure your Node class implemented hashCode and equals

Set<Set<Node>> result = new HashSet<Set<Node>>();
for (Set<Node> s1 : NestedSet) {
    Optional<Set<Node>> findFirst = result.stream().filter(p -> !Collections.disjoint(s1, p)).findFirst();
    if (findFirst.isPresent()){
        findFirst.get().addAll(s1); 
    }
    else {
        result.add(s1);
    }
}

How can I compare two MultiMaps?

Question: I have two Multimaps which have been created from two huge CSV files.

Multimap<String, SomeClassObject> mapOne = ArrayListMultimap.create();
Multimap<String, SomeClassObject> mapTwo = ArrayListMultimap.create();

I have assumed one CSV column to be as a Key and each of the Key has thousands of values associated with it. Data contained within these Multimaps should be same. Now I want to compare the data within these Multimaps and find if any values are different. Here are the two approaches I am thinking of:

Approach One:

Make one big list from the Multimap. This big list will contain a few individual lists. Each of the smaller lists contains a unique value which is the "key" read from Multimap along with its associated values, which will form the rest of that individual list.

ArrayList<Collection<SomeClassObject>> bigList = new ArrayList<Collection<SomeClassObject>>();

Within bigList will be individual small lists A, B, C etc.

I plan on picking individual lists from each bigList of the two files on the basis of checking that individual list from second Multimap contains that "key" element. If it does, then compare both of these lists and find anything that could not be matched.

Approach Two:

Compare both the Multimaps but I am not sure how will that be done.

Which approach should have smaller execution time? I need the operation to be completed in minimum amount of time.

Answer: Use Multimaps.filterEntries(Multimap, Predicate).

If you want to get the differences between two Multimaps, it's very easy to write a filter based on containsEntry, and then use the filtering behavior to efficiently find all the elements that don't match. Just build the Predicate based on one map, and then filter the other.

Here's what I mean. Here, I'm using Java 8 lambdas, but you can look at the revision history of this post to see the Java 7 version:

public static void main(String[] args) {
  Multimap<String, String> first = ArrayListMultimap.create();
  Multimap<String, String> second = ArrayListMultimap.create();

  first.put("foo", "foo");
  first.put("foo", "bar");
  first.put("foo", "baz");
  first.put("bar", "foo");
  first.put("baz", "bar");

  second.put("foo", "foo");
  second.put("foo", "bar");
  second.put("baz", "baz");
  second.put("bar", "foo");
  second.put("baz", "bar");

  Multimap<String, String> firstSecondDifference =
      Multimaps.filterEntries(first, e -> !second.containsEntry(e.getKey(), e.getValue()));

  Multimap<String, String> secondFirstDifference =
      Multimaps.filterEntries(second, e -> !first.containsEntry(e.getKey(), e.getValue()));

  System.out.println(firstSecondDifference);
  System.out.println(secondFirstDifference);
}

Output is the element that is not in the other list, in this contrived example:

{foo=[baz]}
{baz=[baz]}

These multimaps will be empty if the maps match.

In Java 7, you can create the predicate manually, using something like this:

public static class FilterPredicate<K, V> implements Predicate<Map.Entry<K, V>> {
  private final Multimap<K, V> filterAgainst;

  public FilterPredicate(Multimap<K, V> filterAgainst) {
    this.filterAgainst = filterAgainst;
  }

  @Override
  public boolean apply(Entry<K, V> arg0) {
    return !filterAgainst.containsEntry(arg0.getKey(), arg0.getValue());
  }
}

Use it as an argument to Multimaps.filterEntries() like this:

Multimap<String, String> firstSecondDifference =
    Multimaps.filterEntries(first, new FilterPredicate(second));

Multimap<String, String> secondFirstDifference =
    Multimaps.filterEntries(second, new FilterPredicate(first));

Ordering with partial explicit and then another order?

Question: What I need is to order a list in a custom way, I'm looking into the correct way and found guava's Ordering api but the thing is that the list I'm ordering is not always going to be the same, and I just need 2 fields to be at the top of the list, for example I have this:

List<AccountType> accountTypes = new ArrayList<>();
AccountType accountType = new AccountType();
accountType.type = "tfsa";
AccountType accountType2 = new AccountType();
accountType2.type = "rrsp";
AccountType accountType3 = new AccountType();
accountType3.type = "personal";
accountTypes.add(accountType3);
accountTypes.add(accountType2);
accountTypes.add(accountType);
//The order I might have is : ["personal", "rrsp", "tfsa"]
//The order I need is first "rrsp" then "tfsa" then anything else

I tried with a custom comparator and using Ordering in Guava library, something like this:

public static class SupportedAccountsComparator implements Comparator<AccountType> {
    Ordering<String> ordering = Ordering.explicit(ImmutableList.of("rrsp", "tfsa"));
    @Override
    public int compare(AccountType o1, AccountType o2) {
        return ordering.compare(o1.type, o2.type);
    }
}

but it throws an exception because explicit ordering doesnt support other items that are not in the list you provided, is there a way to do a partial explicit ordering? something like:

Ordering.explicit(ImmutableList.of("rrsp", "tfsa")).anythingElseWhatever();

Answer: You don't need Guava for this, everything you need is in the Collections API.

Assuming AccountType implements Comparable, you can just provide a Comparator that returns minimum values for "tfsa" and "rrsp", but leaves the rest of the sorting to AccountType's default comparator:

Comparator<AccountType> comparator = (o1, o2) -> {
    if(Objects.equals(o1.type, "rrsp")) return -1;
    else if(Objects.equals(o2.type, "rrsp")) return 1;
    else if(Objects.equals(o1.type, "tfsa")) return -1;
    else if(Objects.equals(o2.type, "tfsa")) return 1;
    else return o1.compareTo(o2);
};
accountTypes.sort(comparator);

If you don't want your other items sorted, just provide a default comparator that always returns 0.