java 使用流和 lambda 将 Map<Integer, List<String>> 展平到 Map<String, Integer>

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/40484985/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 05:15:25  来源:igfitidea点击:

Flatten a Map<Integer, List<String>> to Map<String, Integer> with stream and lambda

javalambdajava-8java-stream

提问by Vongo

I would like to flatten a Mapwhich associates an Integerkey to a list of String, without losing the key mapping. I am curious as though it is possible and useful to do so with streamand lambda.

我想展平一个MapInteger键关联到 列表的String,而不会丢失键映射。我很好奇,好像用streamand这样做是可能和有用的lambda

We start with something like this:

我们从这样的事情开始:

Map<Integer, List<String>> mapFrom = new HashMap<>();

Let's assume that mapFrom is populated somewhere, and looks like:

让我们假设 mapFrom 填充在某处,看起来像:

1: a,b,c
2: d,e,f
etc.

Let's also assume that the values in the lists are unique.

我们还假设列表中的值是唯一的。

Now, I want to "unfold" it to get a second map like:

现在,我想“展开”它以获得第二张地图,例如:

a: 1
b: 1
c: 1
d: 2
e: 2
f: 2
etc.

I could do it like this (or very similarly, using foreach):

我可以这样做(或非常类似地,使用foreach):

Map<String, Integer> mapTo = new HashMap<>();
for (Map.Entry<Integer, List<String>> entry: mapFrom.entrySet()) {
    for (String s: entry.getValue()) {
        mapTo.put(s, entry.getKey());
    }
}

Now let's assume that I want to use lambda instead of nested forloops. I would probably do something like this:

现在让我们假设我想使用 lambda 而不是嵌套for循环。我可能会做这样的事情:

Map<String, Integer> mapTo = mapFrom.entrySet().stream().map(e -> {
    e.getValue().stream().?
    // Here I can iterate on each List, 
    // but my best try would only give me a flat map for each key, 
    // that I wouldn't know how to flatten.
}).collect(Collectors.toMap(/*A String value*/,/*An Integer key*/))

I also gave a try to flatMap, but I don't think that it is the right way to go, because although it helps me get rid of the dimensionality issue, I lose the key in the process.

我也试了一下flatMap,但我认为这不是正确的方法,因为虽然它帮助我摆脱了维度问题,但我在这个过程中丢失了关键。

In a nutshell, my two questions are :

简而言之,我的两个问题是:

  • Is it possible to use streamsand lambdato achieve this?
  • Is is useful (performance, readability) to do so?
  • 是否可以使用streamslambda实现这一目标?
  • 这样做有用吗(性能、可读性)?

回答by Holger

You need to use flatMapto flatten the values into a new stream, but since you still need the original keys for collecting into a Map, you have to map to a temporary object holding key and value, e.g.

您需要使用flatMap将值展平到一个新的流中,但是由于您仍然需要将原始键收集到 a 中Map,因此您必须映射到一个包含键和值的临时对象,例如

Map<String, Integer> mapTo = mapFrom.entrySet().stream()
       .flatMap(e->e.getValue().stream()
                    .map(v->new AbstractMap.SimpleImmutableEntry<>(e.getKey(), v)))
       .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));

The Map.Entryis a stand-in for the nonexistent tuple type, any other type capable of holding two objects of different type is sufficient.

Map.Entry是一个独立的为不存在的元组类型,能够保持不同类型的两个对象的任何其它类型的是足够的。

An alternative not requiring these temporary objects, is a custom collector:

另一种不需要这些临时对象的方法是自定义收集器:

Map<String, Integer> mapTo = mapFrom.entrySet().stream().collect(
    HashMap::new, (m,e)->e.getValue().forEach(v->m.put(v, e.getKey())), Map::putAll);

This differs from toMapin overwriting duplicate keys silently, whereas toMapwithout a merger function will throw an exception, if there is a duplicate key. Basically, this custom collector is a parallel capable variant of

这与toMap静默覆盖重复键不同,而toMap没有合并函数将抛出异常,如果有重复键。基本上,这个自定义收集器是一个并行能力的变体

Map<String, Integer> mapTo = new HashMap<>();
mapFrom.forEach((k, l) -> l.forEach(v -> mapTo.put(v, k)));

But note that this task wouldn't benefit from parallel processing, even with a very large input map. Only if there were additional computational intense task within the stream pipeline that could benefit from SMP, there was a chance of getting a benefit from parallel streams. So perhaps, the concise, sequential Collection API solution is preferable.

但请注意,即使输入地图非常大,该任务也不会受益于并行处理。只有当流管道中有额外的计算密集型任务可以从 SMP 中受益时,才有可能从并行流中受益。所以也许,简洁的、顺序的 Collection API 解决方案更可取。

回答by Marko Topolnik

You should use flatMapas follows:

你应该使用flatMap如下:

entrySet.stream()
        .flatMap(e -> e.getValue().stream()
                       .map(s -> new SimpleImmutableEntry(e.getKey(), s)));

SimpleImmutableEntryis a nested class in AbstractMap.

SimpleImmutableEntry是 中的嵌套类AbstractMap

回答by Supun Wijerathne

Hope this would do it in simplest way. :))

希望这会以最简单的方式做到这一点。:))

mapFrom.forEach((key, values) -> values.forEach(value -> mapTo.put(value, key)));

回答by Aleksander Mielczarek

This should work. Please notice that you lost some keys from List.

这应该有效。请注意,您丢失了 List 中的一些密钥。

Map<Integer, List<String>> mapFrom = new HashMap<>();
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
        .flatMap(integerListEntry -> integerListEntry.getValue()
                .stream()
                .map(listItem -> new AbstractMap.SimpleEntry<>(listItem, integerListEntry.getKey())))
        .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));