「Dictの値をキーにキーを集計し、集計されたキーに対して任意の関数を実行したDictを返す」をJava8でやってみたら「[Java 8][Stream API] オブジェクトの List から Map を生成する方法を模索する」がすっきり書けた

@keithseahus さんの [Dictの値をキーにキーを集計し、集計されたキーに対して任意の関数を実行したDictを返す](http://qiita.com/keithseahus/items/b179b9008d2564f66981) というElixir  に関する投稿を読んだ。
Elixirの事は全く知らないけど、これJava8のStreamAPIだとどうなるんだろうと思ったのでやってみた。

# やってること

Dict(JavaでいうとMap)のキーと値を入れ替えたDictの作成。
Hadoopでいうと[InverseMapper](https://hadoop.apache.org/docs/r2.3.0/api/org/apache/hadoop/mapreduce/lib/map/InverseMapper.html)。

元投稿によるとElixirでは`Enum.group_by/3`というのがあるらしい。
これはJava8の[Collectors.groupingBy(Function<? super T,? extends K> classifier)](http://docs.oracle.com/javase/jp/8/api/java/util/stream/Collectors.html#groupingBy-java.util.function.Function-)に相当するもので、これだと値をマップ(変換)する手段がないのでgroup_byを拡張したということらしい。

# Java8でやってみた

Java8のCollectorsには[groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream]()というのもあって、値のマップもできるのでこれを使ってみた。

```java:
    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);
    }
```

元投稿に合わせてメソッドにしてみる。

```java:
    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 を生成する方法を模索する](http://qiita.com/komiya_atsushi/items/8daac1b90d73b958c725) という投稿にあったコードに似ている気がしたので、作成したgroupingByメソッドをそちらのコードでも使ってみた。

```java:元のコード
    static Map<String, List<String>> byGroupingByAndMapping(List<GroupUser> groupUsers) {
        return groupUsers.stream()
                .collect(Collectors.groupingBy(
                        GroupUser::groupName,
                        Collectors.mapping(
                                GroupUser::userName,
                                Collectors.toList())));
    }
```
```java:今回作成したgroupingBy()メソッドを使用
    static Map<String, List<String>> byGroupingByAndMapping(List<GroupUser> groupUsers) {
        return groupUsers.stream()
                .collect(groupingBy(GroupUser::groupName, GroupUser::userName));
    }
```
単にショートカット的なメソッドを作って使っただけなんだけど、少しスッキリしたような気がする。
頻繁に使うようであればこういうのをユーティリティクラスに用意しておいたほうがいいかもしれない。