西出正美です。有益なことや無益なことなどいろいろ書いています。

JAVAで組み合わせ

JAVAで組み合わせを作成したいと思うこと、ありますよね。
僕はあるので自分用にメモです。

Ruby では組み合わせを作成することができるArray#combinationが用意されています。
これを使えば、例えば下記のように

require 'json'

jsonstr = <<JSON
[
  {
    "price": 432,
    "name": "hoge"
  },
  {
    "price": 531,
    "name": "fuga"
  },
  {
    "price": 568,
    "name": "piyo"
  }
]
JSON

p JSON.load(jsonstr)
    .combination(2)
    .to_a
    .select(&->x{x.reduce(&->x,y{x["price"] + y["price"]}) == 1000})

こうすれば、JSON の商品リストから price の合計が 1000 になるもの(hoge と piyo)を抜き出してくれます。

[[{"price"=>432, "name"=>"hoge"}, {"price"=>568, "name"=>"piyo"}]]

非常にイージー。

ところが、combination は Java には無いんですよね。
これを Java で書きたい時、どうすればいいのか悩みました。
今回は再帰的なやりかたでやってみました。

 * 組み合わせを返す
 *
 * @param parameter 組み合わせを作成するリスト
 * @param combinationCount 組み合わせの個数
 * @return 組み合わせのリスト
 */
private static final <E> List<List<E>> combination(
  final List<E> parameter,
  final long combinationCount) {

  final int parameterSize = parameter.size();

  if (parameterSize < combinationCount) {
    throw new IllegalArgumentException("リストの要素数が組み合わせ個数より少ないです。");
  } else if (combinationCount <= 0) {
    throw new IllegalArgumentException("組み合わせ個数が不正です。");
  } else if (combinationCount == 1) {
    List<List<E>> result = new LinkedList<List<E>>();
    parameter.forEach(x -> {
      List<E> resultWork = new LinkedList<E>();
      resultWork.add(x);
      result.add(resultWork);
    });
    return result;
  } else {
    List<List<E>> result = new LinkedList<List<E>>();
    for (int i = 0; i < parameterSize - combinationCount + 1; i++) {
      List<List<E>> resultWork = combination(
        parameter.subList(1 + i, parameterSize),
        combinationCount - 1);
      for (List<E> resultWorkForEach : resultWork) {
        resultWorkForEach.add(0, parameter.get(i));
      }
      result.addAll(resultWork);
    }
    return result;
  }
}

LinkedList を使っているのは先頭追加があるためです。
ちょっと見た目がヘビーですが、使えます。
こんな感じです。

class Item {
  public Item(int price, String name){
    this.price = price;
    this.name = name;
  }
  int price;
  String name;

  @Override
  public String toString() {
    return name + ":" + price + "円";
  }
}

class Main {
  public static void main(String[] args) {
    List<Item> itemList = Arrays.asList(new Item[]{
      new Item(432, "hoge"),
      new Item(531, "fuga"),
      new Item(568, "piyo"),
      new Item(999, "meta")
    });

    combination(itemList,3)
      .stream()
      .forEach(x->{System.out.println(Arrays.toString(x.toArray()));});
  }
}

こうなります。

[hoge:432, fuga:531, piyo:568]
[hoge:432, fuga:531, meta:999]
[hoge:432, piyo:568, meta:999]
[fuga:531, piyo:568, meta:999]

Ruby の例題と同じようにこうすると、

combination(itemList,2)
  .stream()
  .filter(x-> x.stream().map(y -> y.price).reduce((y, z)-> y + z).get() == 1000)
  .forEach(x->{System.out.println(Arrays.toString(x.toArray()));});

こうなります。

[hoge:432, piyo:568]

java8 の stream なのに遅延評価が役に立たないのは、Ruby も combination をEnumerator::Lazyで使えないのと一緒ですね。
java8 は遅延評価がデフォルトとなっているので、気軽に limit(1)などで一瞬で返ってくるつもりでいたら combination 部分が延々返ってこない…なんてことが発生しないようになのかもしれません。

Ruby で使えた combination が無くて困った話でした。

← 新しいレポート
Vimで暗号化
過去のレポート →
はじめまして
NISHIDEMASAMI.GITHUB.IO
NISHIDE, Masami

西出正美です。有益なことや無益なことなどいろいろ書いています。

©NISHIDE, Masami Some Rights Reserved