gerges gerges - 2 months ago 19
Java Question

How to translate list A,B to keyed map of tuples with guava

I apologize if this question is a duplicate, searching was difficult as I was unsure of the proper name for what I'm trying to accomplish. The simplest explanation would be

List<A>, List<B> into Map<Key, Tuple<A,B>> where A.Key matched B.Key


To clarify: I have a list of A object and B object that share a key. I'd like to then correlate these two lists into a map where the key matches into a map of key, and tuple A,B.

I've played around with many ideas on how to do this in my head, but most of them end with me feeling like I've misused the library (such as Maps.uniqueIndex, and Iterables.transform). Can anyone point me in the right direction?

Answer

There are no tuple (pair etc.) implementations in Guava. (It's another discussion if it's good idea to implementation tuples in Java at all.) The natural mapping I would suggest is to use a Multimap:

List<A> as = Lists.newArrayList(new A(1, "a"), new A(3, "c"), new A(2, "b"));
List<B> bs = Lists.newArrayList(new B(1, 2), new B(3, 6), new B(5, 10));

Function<WithKey, Object> toKey = new Function<WithKey, Object>() {
    @Override public Object apply(WithKey input) { return input.key(); }
};
ImmutableListMultimap<Object, AbstractWithKey> index = 
    Multimaps.index(Iterables.concat(as, bs), toKey);

or

Multimap<Object, WithKey> m = ArrayListMultimap.create();
for (WithKey w : Iterables.concat(as, bs)) m.put(w.key(), w);

You have to check your invariants before using the multimap (or while your iterating over the multimap entries) for example there could be keys with only a A or B instance. (This shouldn't be a performance issue as it can be done lazily with Iterables.filter.)

Duplicates of one type is another issue. You could check them or use a HashMultimap to ignore them. You could even build a multimap with a constrainted set for values that checks that a value is unique (see Multimaps.newSetMultimap(Map> map, Supplier> factory) and Constraints.constrainedSet(Set set, Constraint constraint)). This has the advantage that it fails fast.

With these A and B implementations:

interface WithKey {
    Object key();
}
abstract class AbstractWithKey implements WithKey {
    Object key;
    Object v;
    @Override public Object key() { return key; }
    @Override public String toString() { 
        return MoreObjects.toStringHelper(this).add("k", key).add("v", v).toString(); 
    }
}
class A extends AbstractWithKey {
    public A(int i, String v) { 
        key = i;
        this.v = v;
    } 
}
class B extends AbstractWithKey {
    public B(int i, int v) { 
        key = i;
        this.v = v;
    }
}

the output is:

{1=[A{k=1, v=a}, B{k=1, v=2}], 2=[A{k=2, v=b}], 3=[A{k=3, v=c}, B{k=3, v=6}], 5=[B{k=5, v=10}]}

Update:

If you have to end up with your tuple instances, you can transform the Multimap.

Multimap<Object, WithKey> m = ArrayListMultimap.create(); 
for (WithKey w : Iterables.concat(as, bs)) m.put(w.key(), w);

Function<Collection<WithKey>, Tuple> f = 
    new Function<Collection<WithKey>, Tuple>(){
    @Override public Tuple apply(Collection<WithKey> input) {
        Iterator<WithKey> iterator = input.iterator();
        return new Tuple(iterator.next(), iterator.next());
    } };
Map<Object, Tuple> result = Maps.transformValues(m.asMap(), f);

Output ((a,b) is the tuple syntax):

{1=(A{k=1, v=a},B{k=1, v=2}), 3=(A{k=3, v=c},B{k=3, v=6})}
Comments