展平地图<整数,列表< String>> to Map< String,Integer>与流和lambda

展平地图<整数,列表< String>> to Map< String,Integer>与流和lambda

问题描述:

我想展平地图,它将整数键与 String ,不会丢失键映射。
我很好奇,好像使用 stream lambda 这样做是有用和有用的。

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

我们从这样开始:

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

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

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

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.

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

I could do it like this (or very similarly, using 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());
    }
}

现在让我们假设我想使用lambda代替嵌套用于循环。我可能会这样做:

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

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*/))

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

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.

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

In a nutshell, my two questions are :


  • 是否有可能使用 streams lambda 来实现这个目标?

  • 是否有用(性能) ,可读性)这样做?

  • Is it possible to use streams and lambda to achieve this?
  • Is is useful (performance, readability) to do so?

你需要使用 flatMap 将值展平为新流,但由于您仍需要原始密钥来收集到 Ma p ,你必须映射到一个持有键和值的临时对象,例如

You need to use flatMap to 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.

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

Map.Entry 是 - 对于不存在的元组类型,任何其他能够容纳两个不同类型对象的类型就足够了。

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

不需要这些临时对象的替代方法是自定义收集器: / p>

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

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

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

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

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

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.