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
-
20-12-2019 - |
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
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.