@keithseahus さんの Dictの値をキーにキーを集計し、集計されたキーに対して任意の関数を実行したDictを返す というElixir に関する投稿を読んだ。
Elixirの事は全く知らないけど、これJava8のStreamAPIだとどうなるんだろうと思ったのでやってみた。
やってること
Dict(JavaでいうとMap)のキーと値を入れ替えたDictの作成。
HadoopでいうとInverseMapper。
元投稿によるとElixirではEnum.group_by/3
というのがあるらしい。
これはJava8のCollectors.groupingBy(Function super T,? extends K> classifier)に相当するもので、これだと値をマップ(変換)する手段がないのでgroup_byを拡張したということらしい。
Java8でやってみた
Java8のCollectorsにはgroupingBy(Function super T,? extends K> classifier, Collector super T,A,D> downstreamというのもあって、値のマップもできるのでこれを使ってみた。
public static void main(String[] args) {
Map<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", 0);
map.put("c", 4);
map.put("d", 0);
map.put("e", 1);
map.put("f", 1);
Map<Integer, List<String>> result = map.entrySet()
.stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
));
System.out.println(result);
}
元投稿に合わせてメソッドにしてみる。
public static void main(String[] args) {
Map<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", 0);
map.put("c", 4);
map.put("d", 0);
map.put("e", 1);
map.put("f", 1);
Map<Integer, List<String>> result = map.entrySet()
.stream()
.collect(groupingBy(Map.Entry::getValue, Map.Entry::getKey));
System.out.println(result); // => {0=[b, d], 1=[a, e, f], 4=[c]}
}
private static <T, K, V> Collector<T, ?, Map<K, List<V>>> groupingBy(
Function<T, K> keyMapper,
Function<T, V> valueMapper
) {
return Collectors.groupingBy(
keyMapper,
Collectors.mapping(valueMapper, Collectors.toList())
);
}
どこかで見た気が...
とここまで書いて少し前に見た @komiya_atsushi さんの [Java 8][Stream API] オブジェクトの List から Map を生成する方法を模索する という投稿にあったコードに似ている気がしたので、作成したgroupingByメソッドをそちらのコードでも使ってみた。
static Map<String, List<String>> byGroupingByAndMapping(List<GroupUser> groupUsers) {
return groupUsers.stream()
.collect(Collectors.groupingBy(
GroupUser::groupName,
Collectors.mapping(
GroupUser::userName,
Collectors.toList())));
}
static Map<String, List<String>> byGroupingByAndMapping(List<GroupUser> groupUsers) {
return groupUsers.stream()
.collect(groupingBy(GroupUser::groupName, GroupUser::userName));
}
単にショートカット的なメソッドを作って使っただけなんだけど、少しスッキリしたような気がする。
頻繁に使うようであればこういうのをユーティリティクラスに用意しておいたほうがいいかもしれない。