Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zip two lists into an immutable multimap in Java 8 with Guava?

The for loop looks like

ImmutableListMultiMap.<Key, Value>Builder builder 
    = ImmutableListMultiMap.<Key, Value>newBuilder();
for (int i = 0; i < Math.min(keys.length(), values.length()); i++) {
  builder.put(keys.at(i), values.at(i));
}

A possible first step in Guava / Java 8 is

Streams.zip(keys, values, zippingFunction)

I think zippingFunction needs to return a map entry, but there isn't a publicly constructable list entry. So the "most" functional way I can write this is with a zipping function that returns a Pair, which I'm not sure exists in Guava, or returns a two-element list, which is a mutable type that does not properly connote there are exactly 2 elements.

This would be desired if I could create a map entry:

Streams.zip(keys, values, zippingFunction)
.collect(toImmutableListMultimap(e -> e.getKey(), e.getValue())

This seems like the best way, except it's not possible, and the zip into entries and unzip from entries still seems roundabout. Is there a way to make this possible or a way it can be improved?

like image 552
djechlin Avatar asked Dec 14 '22 17:12

djechlin


2 Answers

If your lists are random access, you can do it without zipping:

Map<Key, Value> map = IntStream.range(0, Math.min(keys.size(), values.size()))
    .boxed()
    .collect(toImmutableListMultimap(i -> keys[i], i -> values[i]));
like image 97
fps Avatar answered Dec 16 '22 06:12

fps


I think your procedural code is the most optimal solution already (both in terms of memory and speed, assuming random access lists). With small corrections, so that your code compiles, it would be:

ImmutableListMultimap.Builder<Key, Value> builder = ImmutableListMultimap.builder();
for (int i = 0; i < Math.min(keys.size(), values.size()); i++) {
  builder.put(keys.get(i), values.get(i));
}
return builder.build();

If you really want to use streams in order to "be functional", zipping two streams is the way to go, but you'd still have to create intermediate "pair" objects before collecting to multimap. You claim that "there isn't a publicly constructable list entry", but it's not true, there are JDK's SimpleImmutableEntry and Guava's Maps.immutableEntry you can use here (and they fit better than more generic Pair, which, in fact, cannot be found in both JDK or Guava.

Using Streams#zip requires passing streams, so the final code would look like this:

Streams.zip(keys.stream(), values.stream(), SimpleImmutableEntry::new)
    .collect(toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue));

If you're open to using other "functional" Java libraries which allow more stream-related operations, you could use jOOL and its Seq.zip, which accept iterable parameters:

    Seq.zip(keys, values, SimpleImmutableEntry::new)
        .collect(toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue));

Another library would be StreamEx, which exposes EntryStream - an abstraction for key-value pair streams.

like image 35
Xaerxess Avatar answered Dec 16 '22 07:12

Xaerxess