Java
gs-collections
stream-api

Eclipse Collections に挑む

More than 1 year has passed since last update.

概要

昨日参加したJJUG CCC 2015 Fall のセッションでEclipse Collections の紹介がありました。
かつてGoldman SachsがGS Collectionsとして公開していたCollection Framework をEclipse Foundationに移管したもので、12月中のリリースを予定しているとのことでした。

どうすごいのか

詳しくはほかの方が書かれているので軽く書いておくに留めます。なお、一部JavaOne報告会での内容を含みます。

Java8のstream APIと同じことができる

1.短く書ける
2.わかりやすく書ける(filter ってinするの?outするの?Java標準のはinするメソッド)……EC では select/reject で明瞭

品質が高い

標準のHashSetはHashMapのKeyだけを使うという驚愕の実装でメモリ効率がひどい。そんなHashSetと比較してメモリ効率が最大6倍

標準ライブラリに存在しない便利なCollectionを実装

  1. primitive type の Collection をサポート(IntSet)
  2. Bag/Pair/MultiMap/BiMap/Partition が全部ある……標準でやる場合は全部 Map でやらないとダメ

Java 5からサポート

Android で使えるようです。

Stream の使い回しが可能, メモリにキャッシュできる

Stream APIのStream Objectは1回しか操作できない。

LambdaのException handlingにProceduresクラスのthrowing APIがある

これはLambdaのいけてない点として多くの方々が挙げているのでナイスとしか言いようがないです

Kata という教育プログラムが用意されている

Hobson's choice(選択肢があるようで実はない) の回避

「どっちもできる」ことが大事


問題を解きながら学ぶ Eclipse Collections

スライドの最後に問題がありましたので、実際にJava8のStream APIを使って解いてみました。

問題1

簡易なショッピングカートを実装したいと思います。
アイテムは単純な文字列とし、下記の操作をした後、アイテムの文字列順にカート内の個数を出力します。

操作1:“Item B”をカートに追加
操作2:“Item A”をカートに追加
操作3:“Item C”を3個カートに追加
操作4:“Item A”をカートに追加

出力例:

“Item Aが2個あります。”
“Item Bが1個あります。”
“Item Cが3個あります。”

解答例

問題1の解答
    public static void main(final String[] args) throws IOException {
        final Map<String, Integer> map = new HashMap<>();
        putAndIncrement(map, "Item B");
        putAndIncrement(map, "Item A");
        putAndIncrement(map, "Item C");
        putAndIncrement(map, "Item C");
        putAndIncrement(map, "Item C");
        putAndIncrement(map, "Item A");

        new TreeSet<String>(map.keySet()).stream().forEach(
             (key) -> System.out.println(String.format("%sが%d個あります。", key, map.get(key).intValue()))
        );
    }

    private static void putAndIncrement(final Map<String, Integer> map, final String item) {
        map.put(item, map.containsKey(item) ? map.get(item) + 1 : 1);
    }

早くも嫌な予感がしてきました

問題2

プレゼントをランダムに決める機能を実装したいと思います。下記のような文字列が与えられたとして、

メンバー:"Hiroshi", "Yusuke", "Fumio", "Shotaro", "Tatsuya", "Sumire"
プレゼント:"Smart Phone", "PC", "Video Game", "Guitar", "Robot", "Media Player“  

メンバーとプレゼントをそれぞれランダムに対応づけて、下記のような文字列を出力します。

出力例:

 ShotaroはSmart Phoneを手に入れました!
 SumireはPCを手に入れました!
 HiroshiはVideo Gameを手に入れました!
 FumioはGuitarを手に入れました!
 TatsuyaはRobotを手に入れました!
 YusukeはMedia Playerを手に入れました! 

解答例

問題2の解答
        final List<String> members
            = Arrays.asList("Hiroshi", "Yusuke", "Fumio", "Shotaro", "Tatsuya", "Sumire");
        final List<String> presents
            = Arrays.asList("Smart Phone", "PC", "Video Game", "Guitar", "Robot", "Media Player");
        final Queue<Integer> randoms = new LinkedBlockingQueue<>(members.size());
        IntStream.range(0, members.size()).forEach(i -> {
            int random = (int) (Math.random() * members.size());
            while (randoms.contains(random)) {
                random = (int) (Math.random() * members.size());
            }
            randoms.add(random);
        });
        members.stream().forEach(mem -> {
            System.out.println(
                    String.format("%sは%sを手に入れました!", mem, presents.get(randoms.poll())));
        });

問題3

コレクションの分割処理を実装したいと思います。1,000,000要素あるコレクションを、2,000要素ずつ分割処理します。

解答例

問題3の解答
    final List<Integer> list = new ArrayList<>(100);
    IntStream.range(1, 1_000_000).forEach(i -> list.add(i));

    final List<List<Integer>> lists = new ArrayList<>();
    int index = 0;
    while (index < list.size()) {
        final List<Integer> splited = new ArrayList<>();
        final int lastIndex = Math.min(index + 2_000, list.size() + 1);
        IntStream.range(index, lastIndex).forEach(i -> splited.add(i));
        lists.add(splited);
        index = lastIndex;
    }
    lists.stream().forEach(l -> System.out.println(l));

問題4

下記のように、2種類の集合があるとします。

バンドメンバー:"Ken", "Irene", "Hiroshi"
Javaコミュニティメンバー:"Fumio", "Sumire", "Yusuke", "Hiroshi“  

それぞれの集合から、下記のような文字列の出力を得るプログラムを書きます。

出力例:

 バンドとJavaコミュニティ両方に所属: Hiroshi
 バンドまたはJavaコミュニティに所属: Fumio, Sumire, Ken, Hiroshi, Irene, Yusuke
 バンドのみ所属: Ken, Irene
 Javaコミュニティのみ所属: Fumio, Sumire, Yusuke 

解答例

問題4の解答
    final List<String> bandMembers = Arrays.asList("Ken", "Irene", "Hiroshi");
    final List<String> javaMembers = Arrays.asList("Fumio", "Sumire", "Yusuke", "Hiroshi");

    System.out.println("バンドとJavaコミュニティ両方に所属:"
        + bandMembers.stream().filter(bm -> {return javaMembers.contains(bm);})
            .collect(Collectors.toList())
        );
    System.out.println("バンドまたはJavaコミュニティに所属:" + new ArrayList<String>(){{
        addAll(bandMembers);
        addAll(javaMembers);
    }});
    System.out.println("バンドのみ所属:"
        + bandMembers.stream().filter(bm -> {return !javaMembers.contains(bm);})
        .collect(Collectors.toList())
        );
    System.out.println("Javaコミュニティのみ所属:"
        + javaMembers.stream().filter(bm -> {return !bandMembers.contains(bm);})
        .collect(Collectors.toList())
        );

問題5

下記のような文字列のコレクションから、数字のみ抽出して別の文字列コレクションを作りたいと思います。

["[A12345]", "[A99234]", "[A43156]", "[A55324]“]  

出力例

[12345, 99234, 43156, 55324] 

解答例

問題5の解答
    final List<String> strs = Arrays.asList("[A12345]", "[A99234]", "[A43156]", "[A55324]");

    final List<String> ints = new ArrayList<String>(strs.size());
    strs.stream().forEach(str -> {
        final StringBuilder sb = new StringBuilder(str.length());
        IntStream.range(0, str.length()).forEach(i -> {
            sb.append(Character.isDigit(str.charAt(i)) ? str.charAt(i) : "");
        });
        ints.add(sb.toString());
    });
    System.out.println(ints.toString());

まとめ

疲れました。Eclipse Collections 期待大です!!!

GitHub Repo

今回の解答は下記のリポジトリにアップしました。
https://github.com/toastkidjp/ec_challenge/tree/master