tmn
tmn

Reputation: 11569

Creating an ImmutableMap<P,ImmutableMultimap<C,V>> stream Collector

I am frequently running into situations where I need a map of multi-maps for the sake of efficiency. I'd prefer to use Guava's ImmutableMap and ImmutableMultimap to accomplish this.

I have borrowed and created several Collector implementations for Guava so I can leverage Java 8 streams. For example, here is a collector for an ImmutableListMultimap.

public static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper) {

    Supplier<ImmutableListMultimap.Builder<K, V>> supplier = ImmutableListMultimap.Builder::new;

    BiConsumer<ImmutableListMultimap.Builder<K, V>, T> accumulator = (b, t) -> b
            .put(keyMapper.apply(t), valueMapper.apply(t));

    BinaryOperator<ImmutableListMultimap.Builder<K, V>> combiner = (l, r) -> l.putAll(r.build());

    Function<ImmutableListMultimap.Builder<K, V>, ImmutableListMultimap<K, V>> finisher = ImmutableListMultimap.Builder::build;

    return Collector.of(supplier, accumulator, combiner, finisher);
}

I would like to create a very similar Collector for my current problem. I want my collector to create an ImmutableMap<P,ImmutableMultimap<C,V>>, where P is the parent key of the main map and C is the child key of the child map. Two Function lambdas would be provided to map these keys for each T item.

This is much easier said than done. All I did productive so far is create the method stub.

public static <T, P, C, V> Collector<T, ?, ImmutableMap<P, ImmutableMultimap<C,V>>> toPartitionedImmutableMultimap(
            Function<? super T, ? extends P> parentKeyMapper,
            Function<? super T, ? extends C> childKeyMapper,
            Function<? super T, ? extends V> valueMapper) {

}

Because the Guava immutable collection builders do not allow lookups, I found myself using mutable hashmaps to be able to look up previously captured values so I would only create a new ImmutableMultimap when the P key was absent. But this process became dizzying very quickly.

Is there an efficient way to do this?

Upvotes: 4

Views: 2039

Answers (2)

Misha
Misha

Reputation: 28153

Have you tried the straightforward approach?

collectingAndThen(
        groupingBy(
                parentKeyMapper,
                toImmutableListMultimap(childKeyMapper, valueMapper)
        ),
        ImmutableMap::copyOf
);

Update: Above code works fine with JDK but Eclipse compiler is complaining about it. Here's a version that Eclipse will accept:

public static <T, P, C, V> Collector<T, ?, ImmutableMap<P, ImmutableMultimap<C, V>>> toPartitionedImmutableMultimap(
        Function<? super T, ? extends P> parentKeyMapper,
        Function<? super T, ? extends C> childKeyMapper,
        Function<? super T, ? extends V> valueMapper) {

    return Collectors.collectingAndThen(
            Collectors.groupingBy(
                    parentKeyMapper,
                    SO29417692.<T,C,V>toImmutableListMultimap(childKeyMapper, valueMapper)
            ),
            ImmutableMap::<P,ImmutableMultimap<C,V>>copyOf
    );
}

Upvotes: 5

muued
muued

Reputation: 1676

I don't see a way to use the Guava builders for the outer map, either. Yet involving the mutable hashmaps isn't really that dizzying, is it? Using the ImmutableMap.Builder and your toImmutableListMultimap, one easily gets an ImmutableMap-Collector. This can be used in the finisher for the inner multimap yielding the following code:

public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper) {
    final Supplier<ImmutableMap.Builder<K, V>> supplier = ImmutableMap.Builder::new;

    final BiConsumer<ImmutableMap.Builder<K, V>, T> accumulator = (b, t) -> b
            .put(keyMapper.apply(t), valueMapper.apply(t));

    final BinaryOperator<ImmutableMap.Builder<K, V>> combiner = (l, r) -> l
            .putAll(r.build());

    final Function<ImmutableMap.Builder<K, V>, ImmutableMap<K, V>> finisher = ImmutableMap.Builder::build;

    return Collector.of(supplier, accumulator, combiner, finisher);
}

public static <T, P, C, V> Collector<T, Map<P, ImmutableListMultimap.Builder<C, V>>, ImmutableMap<P, ImmutableMultimap<C, V>>> toPartitionedImmutableMultimap(
        Function<? super T, ? extends P> parentKeyMapper,
        Function<? super T, ? extends C> childKeyMapper,
        Function<? super T, ? extends V> valueMapper) {
    final Supplier<Map<P, ImmutableListMultimap.Builder<C, V>>> supplier = HashMap::new;

    final BiConsumer<Map<P, ImmutableListMultimap.Builder<C, V>>, T> accumulator = (
            map, element) -> map.computeIfAbsent(
            parentKeyMapper.apply(element),
            x -> ImmutableListMultimap.builder()).put(
            childKeyMapper.apply(element), valueMapper.apply(element));

    final BinaryOperator<Map<P, ImmutableListMultimap.Builder<C, V>>> combiner = (
            l, r) -> {
        l.putAll(r);
        return l;
    };

    final Function<Map<P, ImmutableListMultimap.Builder<C, V>>, ImmutableMap<P, ImmutableMultimap<C, V>>> finisher = map -> map
            .entrySet()
            .stream()
            .collect(
                    toImmutableMap(Map.Entry::getKey, e -> e.getValue()
                            .build()));

    return Collector.of(supplier, accumulator, combiner, finisher);
}

Upvotes: 2

Related Questions