按照字符串分组列表

5

我有这个类:

class A {
    private List<String> keys;
    private String otherData;
    private int otherDate2;

    // getter and setters for each
}

对于这个类,我有一个简单的列表,其中填充了一些数据。 List<A> listOfA。 现在我想将这些数据转换为地图。 Map<String,List<A>> 目前我们使用一堆方法以非常复杂的方式实现此目的。 我认为,我们可以通过简单的stream()操作来解决它。
我尝试过这个:
// first
listOfA.stream()
    .collect(Colletors.groupingBy(a -> a.getKeys()))
// produces a Map<String, List<A>>     

// second
listOfA.stream()
    .flatMap(a -> a.getKeys().stream())
    .collect(Colletors.groupingBy(string -> string))
// produces a Map<String, List<String>>

这种情况下正确的方式是什么?
编辑:明确一下,我想要一个 Map<String, List<A>>

哪一个适合您的需求? - Bhesh Gurung
这取决于您的分组标准。如果您要按字符串列表分组,那么按keys分组是否有意义? - LuCio
"A.keys" 有时是相等的。我想把相同的键分组在一起。 - akop
2
我理解的是,您希望在结果映射中为A实例的keys列表中的每个条目都有一个条目。 - Henrik Aasted Sørensen
是的。一个映射,使用A.keys作为键本身(不是列表)。 - akop
1
如果 keys 包含 [value,value2],您想要使用 {[value1= A()],[value2=A()]} 进行映射,是这样吗?列表中的每个值都应该是映射中的键。 - Ryuzaki L
3个回答

5

你不需要使用流(streams)来实现这个,用这种方式会更简单:

Map<String, List<A>> result = new HashMap<>();

listOfA.forEach(a -> a.getKeys().forEach(key -> 
        result.computeIfAbsent(key, k -> new ArrayList<>()).add(a)));

这段代码遍历外部和内部列表,并使用computeIfAbsent填充一个Map,如果给定的键还没有值,则创建一个空列表,然后将A实例简单地添加到相应的列表中。

1
确实非常整洁,是Java 8新方法的一个很好的例子,除了流之外,还允许编写更简洁的代码。 - davidxxx
是的,.computeIfAbsent() 方法非常好用。 - akop
1
@davidxxx 是他的第二个名字,不使用流 ;) - Eugene

3
第一个代码将按Map<List<String>, List<A>>进行分组,而不是Map<String, List<A>>
第二个代码没有意义:您通过它们本身将字符串分组...
一个简单的方法是创建所有可能的键-A对的集合。您可以为每个对使用一个Map,但这看起来有点冗余。SimpleImmutableEntry表示键和值更适合。一旦您获得整个couple,您就可以很容易地按键对A元素进行分组。
您可以尝试类似以下的内容:
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

    // ...
    List<A> listOfA = new ArrayList<>();
    listOfA.add(new A(Arrays.asList("1", "2"), "foo1", 1));
    listOfA.add(new A(Arrays.asList("2", "3"), "foo2", 2));
    listOfA.add(new A(Arrays.asList("3", "4"), "foo3", 3));

    Map<String, List<A>> map =
    listOfA.stream()
           .flatMap(a -> a.keys.stream()
                               .map(k -> new SimpleImmutableEntry<>(k, a)))
           .collect(groupingBy(e -> e.getKey(), mapping(e -> e.getValue(), toList())));

    map.forEach((k, v) -> System.out.println("key=" + k + ", value=" + v + "\n"));

输出:

键=1,值=[A [keys = [1, 2],otherData = foo1,otherDate2 = 1]]

键=2,值=[A [keys = [1, 2],otherData = foo1,otherDate2 = 1],A [keys = [2, 3],otherData = foo2,otherDate2 = 2]]

键=3,值=[A [keys = [2, 3],otherData = foo2,otherDate2 = 2],A [keys = [3, 4],otherData = foo3,otherDate2 = 3]]

键=4,值=[A [keys = [3, 4],otherData = foo3,otherDate2 = 3]]


这个解决方案是可行的,也正是我所要求的。但它也非常复杂。Federico Peralta Schaffner 的答案更简单。所以他得到了“正确的答案”。虽然只有一个答案可行,但还是非常感谢您的帮助。 :) - akop

0

如果您想按某个字段对给定的集合进行分组,每个组可能包含多个对象。这是我的GroupUtils的一部分:

public static <K, V> Map<K, List<V>> groupMultipleBy(Collection<V> data, Function<V, K> classifier) {
    return Optional.ofNullable(data).orElse(Collections.emptyList()).stream().collect(Collectors.groupingBy(classifier, Collectors.mapping(Function.identity(), Collectors.toList())));
}

使用示例。

您有以下类:

class A {
    private String id;
    private String name;
}

如果要对这个类的集合进行分组,可以使用:

List<A> collection = Collections.emptyList();
Map<String, List<A>> groupedById = GroupUtils.groupMultipleBy(collection, A::getId);

附言

为了给您提供更多选择,这是我的GroupUtils的相关部分:

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class GroupUtils {

    public static <K, V> Map<K, List<V>> groupMultipleBy(Collection<V> data, Function<V, K> classifier) {
        return groupMultipleBy(data, classifier, Function.identity());
    }

    public static <K, V, S> Map<K, List<S>> groupMultipleBy(Collection<V> data, Function<V, K> classifier, Function<V, S> mapper) {
        return groupMultipleBy(data, classifier, mapper, Collectors.toList());
    }

    public static <K, V, S, R extends Collection<S>> Map<K, R> groupMultipleBy(Collection<V> data, Function<V, K> classifier,
                                                                               Collector<S, ?, R> downstream) {
        return groupMultipleBy(data, classifier, (Function<V, S>)Function.identity(), downstream);
    }

    public static <K, V, S, R extends Collection<S>> Map<K, R> groupMultipleBy(Collection<V> data, Function<V, K> classifier,
                                                                               Function<V, S> mapper, Collector<S, ?, R> downstream) {
        return Optional.ofNullable(data).orElse(Collections.emptyList()).stream()
                       .collect(Collectors.groupingBy(classifier, Collectors.mapping(mapper, downstream)));
    }

    public static <K, V> Map<K, V> groupSingleBy(Collection<V> data, Function<V, K> keyMapper) {
        return groupSingleBy(data, keyMapper, Function.identity());
    }

    public static <K, V, S> Map<K, S> groupSingleBy(Collection<V> data, Function<V, K> keyMapper, Function<V, S> valueMapper) {
        return Optional.ofNullable(data).orElse(Collections.emptyList()).stream().collect(Collectors.toMap(keyMapper, valueMapper));
    }

}

什么是“分类器”,我该怎样使用它? - akop
@user6537157 请看示例 - oleg.cherednik
我无法复现你的方法。它太复杂了。这个方法体非常复杂,没有人能理解 public static <K,V,S,R extends Collection<S>> Map<K,R> groupMultipleBy - akop
没问题。这是你如何解决问题的选择。我的解决方案并不比标准的collect group by更复杂。 - oleg.cherednik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接