Der sauberste Weg, eine Sammlung anhand einer Eigenschaft des Elements zu indizieren, das selbst eine Sammlung ist

StackOverflow https://stackoverflow.com//questions/23003754

Frage

Ich habe ein List<Foo> und möchte eine Guave Multimap<String, Foo> wo wir die gruppiert haben Fooist bei jedem ihrer Tags angegeben Collection<String> getTags() Funktion.

Ich verwende Java 8, daher sind Lambdas und Methodenreferenzen in Ordnung bzw. werden empfohlen.

Wenn ich zum Beispiel:

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

Ich würde eins bekommen Multimap<String, Foo> mit:

a -> foo1, foo3
b -> foo1
c -> foo1, foo2, foo3
d -> foo2
e -> foo3
War es hilfreich?

Lösung

Sie können hierfür einen benutzerdefinierten Collector verwenden:

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();

Dies verursacht keine zusätzlichen Nebenwirkungen (siehe Hier dazu) ist gleichzeitig und idiomatischer.

Sie können diese Ad-hoc-Lambdas auch in einen vollwertigen Collector extrahieren, etwa so:

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();
    }
}

Dann würde die Sammlung so aussehen:

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

Sie können auch zurückkehren EnumSet.of(Characteristics.UNORDERED) aus characteristics() Methode, wenn die Reihenfolge für Sie nicht wichtig ist.Dies kann dazu führen, dass die internen Sammelmechanismen effizienter arbeiten, insbesondere bei paralleler Reduzierung.

Andere Tipps

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

Dies ist für Java 8-Streams etwas idiomatischer:

    Multimap<String, Foo> map = list.stream()
            //First build a stream of Pair<String, Foo>
            .flatMap(f -> f.tags.stream().map(s -> new AbstractMap.SimpleImmutableEntry<>(s, f)))
            //Then collect it up into a multimap.
            .collect(
                    Multimaps.toMultimap(
                            x -> x.getKey(),
                            x -> x.getValue(),
                            MultimapBuilder.hashKeys().arrayListValues()::build
                    )
            );

Ich weiß, dass die Verwendung einer Paarklasse zwar hässlich ist, aber das Wichtigste, was ich zeigen wollte, ist das Multimaps.toMultmap Kollektor.Es gibt auch ein Multmaps.flatteningToMultimap für andere Anwendungsfälle.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top