Façon la plus propre de l'index d'une Collection par une propriété de l'objet qui est lui-même une collection

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

Question

J'ai un List<Foo> et que vous voulez une goyave Multimap<String, Foo> où nous avons regroupé les Foo's par chaque balise de leur Collection<String> getTags() fonction.

Je suis à l'aide de java 8, de sorte que les lambdas et de la méthode références sont beaux/encouragés.

Par exemple, si j'ai:

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

Je voudrais obtenir un Multimap<String, Foo> avec:

a -> foo1, foo3
b -> foo1
c -> foo1, foo2, foo3
d -> foo2
e -> foo3
Était-ce utile?

La solution

Vous pouvez utiliser collecteur personnalisé pour ceci:

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

Ce n'est pas causer des effets secondaires (voir ici sur ce point), est simultanées et les plus idiomatiques.

Vous pouvez également extraire ces ad-hoc lambdas dans un véritable collector, quelque chose comme ceci:

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

Ensuite, la collection devrait ressembler à ceci:

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

Vous pouvez également retourner EnumSet.of(Characteristics.UNORDERED) à partir de characteristics() méthode si l'ordre n'est pas important pour vous.Cela peut rendre l'intérieur de la collection de machines agir plus efficacement, en particulier dans le cas de la réduction parallèle.

Autres conseils

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

C'est un peu plus idiomatiques pour Java 8 volets:

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

Je sais que l'utilisation d'une paire de classe est un mais laid, mais la principale chose que je voulais montrer, c'est la Multimaps.toMultmap collecteur.Il y a aussi un Multmaps.flatteningToMultimap pour les autres cas.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top