【问题标题】:How can I transform a collection into a Guava Multimap grouped by the elements of a nested collection property?如何将集合转换为按嵌套集合属性的元素分组的 Guava Multimap?
【发布时间】:2014-04-11 04:51:47
【问题描述】:

我有一个List<Foo> 并想要一个番石榴Multimap<String, Foo>,我们将Foos 按Collection<String> getTags() 函数的每个标签分组。

我使用的是 Java 8,因此 lambda 和方法引用很好/值得鼓励。

例如,如果我有:

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

我会得到一个Multimap<String, Foo>

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

【问题讨论】:

标签: java guava java-8 java-stream


【解决方案1】:

您可以为此使用自定义收集器:

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

这不会导致额外的副作用(请参阅here),是并发的并且更惯用。

您还可以将这些临时 lambda 提取到成熟的收集器中,如下所示:

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

那么集合将如下所示:

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

如果订单对您不重要,您也可以从characteristics() 方法返回EnumSet.of(Characteristics.UNORDERED)。这可以使内部收集机制更有效地发挥作用,尤其是在并行归约的情况下。

【讨论】:

    【解决方案2】:
    ImmutableMultimap.Builder<String, Foo> builder = ImmutableMultimap.builder();
    list.forEach(foo -> foo.getTags().forEach(tag -> builder.put(tag, foo));
    return builder.build();
    

    【讨论】:

    • 很好,并且可以包装在一个很好的静态助手中。好奇有没有更短的。
    • 这个版本的番石榴中没有。
    【解决方案3】:

    这对于 Java 8 流来说稍微更惯用:

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

    我知道pair类的使用是丑陋的,但我想展示的关键是Multimaps.toMultmap收集器。还有一个 Multmaps.flatteningToMultimap 用于其他用例。

    【讨论】:

      【解决方案4】:

      您可以使用Stream.flatMap 在每个标签/foo 对上创建一个流,然后将其收集到一个多映射中。较新版本的 Guava 为此提供了诸如 ImmutableSetMultimap.toImmutableSetMultimapImmutableListMultimap.toImmutableListMultimap 之类的收集器。

      ImmutableSetMultimap<String, Foo> result = list.stream()
              .flatMap(foo -> foo.getTags().stream().map(tag -> Map.entry(tag, foo)))
              .collect(ImmutableSetMultimap.toImmutableSetMultimap(Map.Entry::getKey, Map.Entry::getValue));
      

      【讨论】:

        猜你喜欢
        • 2012-12-08
        • 2017-11-28
        • 1970-01-01
        • 2015-09-24
        • 2018-06-03
        • 1970-01-01
        • 2021-07-07
        • 1970-01-01
        相关资源
        最近更新 更多