web-dev-qa-db-ja.com

Java8:Stream / Map-Reduce / Collectorを使用したHashMap <X、Y>からHashMap <X、Z>への変換

私は、単純なJavaのListY - > Zから "変換"する方法を知っています。

List<String> x;
List<Integer> y = x.stream()
        .map(s -> Integer.parseInt(s))
        .collect(Collectors.toList());

さて、私は基本的に地図でも同じことをしたいのです。

INPUT:
{
  "key1" -> "41",    // "41" and "42"
  "key2" -> "42      // are Strings
}

OUTPUT:
{
  "key1" -> 41,      // 41 and 42
  "key2" -> 42       // are Integers
}

解決策はString - > Integerに限定されるべきではありません。上記のListの例と同じように、任意のメソッド(またはコンストラクター)を呼び出したいです。

170
Benjamin M
Map<String, String> x;
Map<String, Integer> y =
    x.entrySet().stream()
        .collect(Collectors.toMap(
            e -> e.getKey(),
            e -> Integer.parseInt(e.getValue())
        ));

それはリストコードほど素敵ではありません。 map()呼び出しで新しいMap.Entrysを作成することはできません。そのため、作業はcollect()呼び出しに混在します。

302
John Kugelman

これは Sotirios Delimanolisの答え に対するいくつかのバリエーションです。これは(+1)から始めるとかなり良かったです。次の点を考慮してください。

static <X, Y, Z> Map<X, Z> transform(Map<? extends X, ? extends Y> input,
                                     Function<Y, Z> function) {
    return input.keySet().stream()
        .collect(Collectors.toMap(Function.identity(),
                                  key -> function.apply(input.get(key))));
}

カップルがここを指しています。 1つ目は、総称でのワイルドカードの使用です。これにより、機能が多少柔軟になります。たとえば、入力マップのキーのスーパークラスであるキーを出力マップに含める場合は、ワイルドカードが必要になります。

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<CharSequence, Integer> output = transform(input, Integer::parseInt);

(マップの値の例もありますが、実際には工夫されています。Yの境界ワイルドカードを使用することは、Edgeの場合にのみ役立ちます。)

2つ目のポイントは、入力マップのentrySetの上でストリームを実行するのではなく、keySetの上でそれを実行したということです。これにより、マップエントリからではなくマップから値をフェッチする必要があるという犠牲を払って、コードが少しきれいになります。ちなみに、最初はtoMap()への最初の引数としてkey -> keyを持っていましたが、これは何らかの理由で型推論エラーで失敗しました。 Function.identity()と同様に(X key) -> keyに変更してもうまくいきました。

さらに別の変形は以下の通りである。

static <X, Y, Z> Map<X, Z> transform1(Map<? extends X, ? extends Y> input,
                                      Function<Y, Z> function) {
    Map<X, Z> result = new HashMap<>();
    input.forEach((k, v) -> result.put(k, function.apply(v)));
    return result;
}

これはストリームの代わりにMap.forEach()を使います。これはさらに簡単です、それは私が思う、それはそれが地図で使用するために多少不器用であるコレクターを不要にするので。その理由は、Map.forEach()がキーと値を別々のパラメータとして与えるのに対して、ストリームは1つの値しか持たない - そしてその値としてキーとマップエントリのどちらを使うかを選択しなければならないからです。マイナス面では、これは他のアプローチの豊かで流線形の良さを欠いています。 :-)

33
Stuart Marks

そのような一般的な解決策

public static <X, Y, Z> Map<X, Z> transform(Map<X, Y> input,
        Function<Y, Z> function) {
    return input
            .entrySet()
            .stream()
            .collect(
                    Collectors.toMap((entry) -> entry.getKey(),
                            (entry) -> function.apply(entry.getValue())));
}

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<String, Integer> output = transform(input,
            (val) -> Integer.parseInt(val));
12

それは絶対に100%機能的で流暢である必要がありますか?そうでなければ、これはどうですか、それはそれが得るのと同じくらい短いです:

Map<String, Integer> output = new HashMap<>();
input.forEach((k, v) -> output.put(k, Integer.valueOf(v));

あなたが副作用とストリームを組み合わせることの恥と罪悪感を持って生きることができるなら

9
Lukas Eder

Guavaの関数 Maps.transformValues はあなたが探しているもので、ラムダ式でうまく働きます。

Maps.transformValues(originalMap, val -> ...)
8
Alex Krauss

標準のストリームAPIを強化するMy StreamEx ライブラリは、適切な EntryStream クラスを提供します。マップを変換するのに適しています。

Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();
5
Tagir Valeev

ToMap()JDKコレクターはここでは簡潔です(+1 ここで )。

Map<String,Integer> newMap = givenMap.
                entrySet().
                stream().collect(Collector.of
               ( ()-> new HashMap<String,Integer>(),
                       (mutableMap,entryItem)-> mutableMap.put(entryItem.getKey(),Integer.parseInt(entryItem.getValue())),
                       (map1,map2)->{ map1.putAll(map2); return map1;}
               ));
3
Ira

サードパーティのライブラリを使用しても構わない場合は、my cyclops-react libにすべての拡張機能があります JDK CollectionMap などの型。 'map'演算子を使用して直接マップを変換することができます(デフォルトでは、マップはマップ内の値に作用します)。

   MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                                .map(Integer::parseInt);

bimapはキーと値を同時に変換するために使うことができます

  MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                               .bimap(this::newKey,Integer::parseInt);
3
John McClean

宣言的で簡単な解決策は次のようになります。

yourMutableMap.replaceAll((key、val) - > return_value_of_bi_your_function); Nb。マップの状態を変更することに注意してください。だからこれはあなたが望むものではないかもしれません。

乾杯: http://www.deadcoderising.com/2017-02-14-Java-8-declarative-ways-of-modifying-a-map-using-compute-merge- / と置き換えます

0
Breton F.