Java変換とフィルタリングを行うマップがあります。簡単な例として、すべての値を整数に変換し、奇数のエントリを削除するとします。
_Map<String, String> input = new HashMap<>();
input.put("a", "1234");
input.put("b", "2345");
input.put("c", "3456");
input.put("d", "4567");
Map<String, Integer> output = input.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> Integer.parseInt(e.getValue())
))
.entrySet().stream()
.filter(e -> e.getValue() % 2 == 0)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(output.toString());
_
これは正しく、次のようになります:_{a=1234, c=3456}
_
ただし、.entrySet().stream()
を2回呼び出すことを回避する方法があるのではないかと考えずにはいられません。
変換操作とフィルター操作の両方を実行し、.collect()
を最後に1回だけ呼び出す方法はありますか?
はい。各エントリを、キーと解析された整数値を保持する別の一時エントリにマップできます。次に、値に基づいて各エントリをフィルタリングできます。
Map<String, Integer> output =
input.entrySet()
.stream()
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), Integer.valueOf(e.getValue())))
.filter(e -> e.getValue() % 2 == 0)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
parseInt
の代わりにInteger.valueOf
を使用したことに注意してください。実際には、ボックス化されたint
が必要だからです。
StreamEx ライブラリを使用する余裕がある場合は、非常に簡単に実行できます。
Map<String, Integer> output =
EntryStream.of(input).mapValues(Integer::valueOf).filterValues(v -> v % 2 == 0).toMap();
より少ないオーバーヘッドで問題を解決する1つの方法は、マッピングとフィルタリングをコレクターに移動することです。
_Map<String, Integer> output = input.entrySet().stream().collect(
HashMap::new,
(map,e)->{ int i=Integer.parseInt(e.getValue()); if(i%2==0) map.put(e.getKey(), i); },
Map::putAll);
_
これは、中間の_Map.Entry
_インスタンスの作成を必要とせず、さらに良いことに、値が実際にint
に追加される時点までMap
値のボックス化を延期します。フィルターによって拒否された値は、ボックス化されていません。
Collectors.toMap(…)
の動作と比較して、ここではキーの衝突を処理する必要がないことを事前に知っているので、_Map.put
_ではなく_Map.merge
_を使用することで操作も簡略化されます。
ただし、並列実行を利用したくない場合は、通常のループも考慮することができます
_HashMap<String,Integer> output=new HashMap<>();
for(Map.Entry<String, String> e: input.entrySet()) {
int i = Integer.parseInt(e.getValue());
if(i%2==0) output.put(e.getKey(), i);
}
_
または内部反復バリアント:
_HashMap<String,Integer> output=new HashMap<>();
input.forEach((k,v)->{ int i = Integer.parseInt(v); if(i%2==0) output.put(k, i); });
_
後者は非常にコンパクトで、少なくともシングルスレッドパフォーマンスに関する他のすべてのバリアントと同等です。
グアバ の友達:
Map<String, Integer> output = Maps.filterValues(Maps.transformValues(input, Integer::valueOf), i -> i % 2 == 0);
output
はinput
の変換され、フィルタリングされたviewであることに注意してください。それらを個別に操作する場合は、コピーを作成する必要があります。
Stream.collect(supplier, accumulator, combiner)
メソッドを使用して、エントリを変換し、条件付きで蓄積できます。
Map<String, Integer> even = input.entrySet().stream().collect(
HashMap::new,
(m, e) -> Optional.ofNullable(e)
.map(Map.Entry::getValue)
.map(Integer::valueOf)
.filter(i -> i % 2 == 0)
.ifPresent(i -> m.put(e.getKey(), i)),
Map::putAll);
System.out.println(even); // {a=1234, c=3456}
ここでは、アキュムレータ内で、Optional
メソッドを使用して変換と述語の両方を適用し、オプションの値がまだ存在する場合は、収集中のマップに追加します。
これを行う別の方法は、変換されたMap
から不要な値を削除することです。
Map<String, Integer> output = input.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> Integer.parseInt(e.getValue()),
(a, b) -> { throw new AssertionError(); },
HashMap::new
));
output.values().removeIf(v -> v % 2 != 0);
これは、結果として可変のMap
が必要であることを前提としています。そうでない場合は、output
から不変のMap
を作成できます。
値を同じ型に変換していて、replaceAll
をその場で変更したい場合、これは input
でより短くなる可能性があります。
input.replaceAll((k, v) -> v + " example");
input.values().removeIf(v -> v.length() > 10);
また、これはMap
が可変であると想定しています。
すべての有効なHashMap
実装で動作するわけではなく、将来replaceAll
で動作しなくなる可能性があるため、これを行うことはお勧めしませんが、現在HashMap
とキャストを使用できますMap
を使用して、値のタイプを変更します。
((Map)input).replaceAll((k, v) -> Integer.parseInt((String)v));
Map<String, Integer> output = (Map)input;
output.values().removeIf(v -> v % 2 != 0);
これにより、タイプの安全性に関する警告が表示され、次のような古いタイプの参照を介してClassCastException
から値を取得しようとすると、
String ex = input.get("a");
old
をスローします。
最初の変換部分をメソッドに移動して、ボイラープレートをたくさん使用する場合は、ボイラープレートを回避できます。
public static <K, VO, VN, M extends Map<K, VN>> M transformValues(
Map<? extends K, ? extends VO> old,
Function<? super VO, ? extends VN> f,
Supplier<? extends M> mapFactory){
return old.entrySet().stream().collect(Collectors.toMap(
Entry::getKey,
e -> f.apply(e.getValue()),
(a, b) -> { throw new IllegalStateException("Duplicate keys for values " + a + " " + b); },
mapFactory));
}
そして、次のように使用します。
Map<String, Integer> output = transformValues(input, Integer::parseInt, HashMap::new);
output.values().removeIf(v -> v % 2 != 0);
たとえば、Map
IdentityHashMap
がmapFactory
であり、HashMap
が_ [$ var]を作成する場合、重複キー例外がスローされることに注意してください。 _。
AbacusUtil によるコードです
Map<String, String> input = N.asMap("a", "1234", "b", "2345", "c", "3456", "d", "4567");
Map<String, Integer> output = Stream.of(input)
.groupBy(e -> e.getKey(), e -> N.asInt(e.getValue()))
.filter(e -> e.getValue() % 2 == 0)
.toMap(Map.Entry::getKey, Map.Entry::getValue);
N.println(output.toString());
宣言:私はAbacusUtilの開発者です。