2014-04-11 8 views
11

Ho un List<Foo> e voglio una guava Multimap<String, Foo> in cui abbiamo raggruppato le Foo 's per ciascun tag della loro funzione Collection<String> getTags().modo più pulito per indicizzare una raccolta da una proprietà della voce di che si è una raccolta

Sto usando java 8, quindi i riferimenti di metodo e lambda sono validi/consigliati.

Per esempio se ho:

foo1, tags=a,b,c 
foo2, tags=c,d 
foo3, tags=a,c,e 

vorrei avere un Multimap<String, Foo> con:

a -> foo1, foo3 
b -> foo1 
c -> foo1, foo2, foo3 
d -> foo2 
e -> foo3 
+2

Dal momento che Guava 21 è un built-in [ImmutableListMultimap.toImmutableListMultimap] (https://google.github.io/guava/releases/21.0/api/docs/com/google/common/collect/ImmutableListMultimap.html# toImmutableListMultimap-java.util.function.Function-java.util.function.Function-) collector. –

risposta

12

È possibile utilizzare collezione personalizzata per questo:

Multimap<String, Foo> map = list.stream().collect(
    ImmutableMultimap::builder, 
    (builder, value) -> value.getTags().forEach(tag -> builder.put(tag, value)), 
    (builder1, builder2) -> builder1.putAll(builder2.build()) 
).build(); 

Questo non causa Gli effetti collaterali extra (vedi here su questo), sono concomitanti e più idiomatici.

È possibile anche estrarre questi ad-hoc lambda in un raccoglitore a tutti gli effetti, qualcosa di simile:

public static <T, K> Collector<T, ?, Multimap<K, T>> toMultimapByKey(Function<? super T, ? extends Iterable<? extends K>> keysMapper) { 
    return new MultimapCollector<>(keysMapper); 
} 

private static class MultimapCollector<T, K> implements Collector<T, ImmutableMultimap.Builder<K, T>, Multimap<K, T>> { 
    private final Function<? super T, ? extends Iterable<? extends K>> keysMapper; 

    private MultimapCollector(Function<? super T, ? extends Iterable<? extends K>> keysMapper) { 
     this.keysMapper = keysMapper; 
    } 

    @Override 
    public Supplier<ImmutableMultimap.Builder<K, T>> supplier() { 
     return ImmutableMultimap::builder; 
    } 

    @Override 
    public BiConsumer<ImmutableMultimap.Builder<K, T>, T> accumulator() { 
     return (builder, value) -> keysMapper.apply(value).forEach(k -> builder.put(k, value)); 
    } 

    @Override 
    public BinaryOperator<ImmutableMultimap.Builder<K, T>> combiner() { 
     return (b1, b2) -> b1.putAll(b2.build()); 
    } 

    @Override 
    public Function<ImmutableMultimap.Builder<K, T>, Multimap<K, T>> finisher() { 
     return ImmutableMultimap.Builder<K, T>::build; 
    } 

    @Override 
    public Set<Characteristics> characteristics() { 
     return Collections.emptySet(); 
    } 
} 

Poi la collezione sarebbe simile a questa:

Multimap<String, Foo> map = list.stream().collect(toMultimapByKey(Foo::getTags)); 

È inoltre possibile tornare EnumSet.of(Characteristics.UNORDERED) dal metodo characteristics() se l'ordine non è importante per te. Ciò può rendere più efficiente il meccanismo di raccolta interna, specialmente in caso di riduzione parallela.

7
ImmutableMultimap.Builder<String, Foo> builder = ImmutableMultimap.builder(); 
list.forEach(foo -> foo.getTags().forEach(tag -> builder.put(tag, foo)); 
return builder.build(); 
+0

Bello, e può essere avvolto in un simpatico aiutante statico. Curioso se c'è qualcosa di più breve. –

+0

Non in questa versione di Guava. –

Problemi correlati