NISHIDEMASAMI.GITHUB.IO

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 が無くて困った話でした。

公開日: Nov 26, 2016

このエントリーをはてなブックマークに追加