「第5回 オフラインリアルタイムどう書く」へなちょこ解答(Java)

ここ最近、やる気がモガの森に家出していますが、先週金曜は「第5回 オフラインリアルタイムどう書く」へ行ってきました。

今回は、大貧民です!

有難いことに、初心者向けの部分問題が用意されていました。しかし、それすら1時間で解き終わりませんでした。悔しい(^q^)

いつも通りJavaで回答しました。全文は長いので、リンク先にあります。

ハイライト(?)

組み合わせの列挙

list.size()==n個の中から、r個のものを選び出すコードです。

	protected List<String> loop(List<String> list, int r) {
		List<String> result = new ArrayList<>();
		int n = list.size();

		// n = r のとき、選び方は1つなので、リストをそのまま1つのリストにして返す
		if (n == r) {
			StringBuilder sb = new StringBuilder();
			for (String card : list) {
				sb.append(card);
			}
			result.add(new String(sb));
			return result;
		}

		// r = 1 のとき、選び方はn通りあるので、リストの要素をそれぞれリストにして返す
		if (r == 1) {
			for (String card : list) {
				result.add(card);
			}
			return result;
		}

		// r = 0 または r がリストの要素数より大きいとき、空リストを返す
		if (r == 0 || n < r) {
			return result;
		}

		// 「リストの先頭要素を除いたリストからr-1個を選ぶ組合せのそれぞれに先頭要素を加えたもの
		// と、 リストの先頭要素を除いたリストからr個を選ぶ組合せ」の合計
		List<String> paraList = new ArrayList<>(list);
		String removed = paraList.remove(0);
		List<String> looped = loop(paraList, r - 1);
		for (int i = 0; i < looped.size(); i++) {
			String str = looped.get(i);
			looped.set(i, str + removed);
		}
		List<String> looped2 = loop(paraList, r);
		for (String card : looped2) {
			looped.add(card);
		}
		return looped;
	}

長時間悩みましたが、次のページで公式が自然言語で整理されていたおかげで、なんとか書けました。

カンマ区切り

リストの各要素(の文字列)をカンマ区切りで繋いだ値を得たいとき、Javaで書くと長くなりがちです。

	protected String createCombination(List<String> list, int target) {
		StringBuilder result = new StringBuilder();

		List<String> loop = loop(list, target);
		for (String cards : loop) {
			result.append(cards);
			result.append(',');
		}
		return new String(result).substring(0, result.length() - 1);
	}

Javaには、Rubyのjoinのような関数がないからですね。

GoogleのGuavaライブラリに、いい感じのメソッドがあります。

import com.google.common.base.Joiner;
// 中略
	protected String createCombination(List<String> list, int target) {
		List<String> loop = loop(list, target);
		return Joiner.on(",").join(loop);
	}

非人間的なreturn文から開放されました。

関数オブジェクトみたいなアレ

たとえば、リストの各要素(の文字列)に、ある文字列を足したいとき。

		// 「リストの先頭要素を除いたリストからr-1個を選ぶ組合せのそれぞれに先頭要素を加えたもの
		// と、 リストの先頭要素を除いたリストからr個を選ぶ組合せ」の合計
		List<String> paraList = new ArrayList<>(list);
		String removed = paraList.remove(0);
		List<String> looped = loop(paraList, r - 1);
		for (int i = 0; i < looped.size(); i++) {
			String str = looped.get(i);
			looped.set(i, str + removed);
		}
		List<String> looped2 = loop(paraList, r);
		for (String card : looped2) {
			looped.add(card);
		}

ちょっと勘弁して欲しい感じですが、これもGuavaを使うと、少し幸せ(か?)

		// 「リストの先頭要素を除いたリストからr-1個を選ぶ組合せのそれぞれに先頭要素を加えたもの
		// と、 リストの先頭要素を除いたリストからr個を選ぶ組合せ」の合計
		List<String> paraList = new ArrayList<>(list);
		final String removed = paraList.remove(0);
		List<String> looped = loop(paraList, r - 1);
		Function<String, String> func = new Function<String, String>() {
			public String apply(String input) {
				return input += removed;
			};
		};
		Lists.transform(looped, func);
		List<String> looped2 = loop(paraList, r);
		for (String cardList : looped2) {
			looped.add(cardList);
		}

あれ? おかしい……何か、不幸になってる気がする……。(もっと良い方法がありそう)

コード以外の話

書こう!

Perlの達人のHさんが、「ラクダ本? 読んでませんよ。あんな分厚いの読んでたらいつまでたっても書けない」と(冗談か本当か分かりませんが)おっしゃっていました。

本やマニュアルをよむのも大事だけど、書くのが大事なんだなぁと改めて思いました。

書き直そう!

「解くのはいいけど、書き直してないでしょ」と鍋谷さんからツッ込まれたので、人様の解答のロジックを理解して、書き直したいところ。

Rubyで短く書かれたロジックを追うのは自分にはキツイので、C++解答を読むところからでしょうか。

質問しよう!

新卒1年目の方に、同じ会社の先輩が、「せっかくの機会なんだからもっと質問して」「質問された側を困らせて」と繰り返しおっしゃっていたのが印象的でした。

「質問せい」と言ってくれる先輩と勉強会で同席できるなんて、滅茶苦茶心強い状況だと思いますので、本当に何でも質問なさるといいと思いました。

また、会話に出てきた専門用語を後で調べようともし思ったら、メモを取るのがオススメです。覚えていられると思っても、お酒が入ると忘れる場合もありますし。

とはいうものの、2年前のふつける読書会では自分もほとんど質問できなかったので、いま質問するようになったのは、モヒカンな師匠による「思ったことは何でも口に出せ」という教えの賜物だと思います。(素晴らしい言葉ですね!)

というわけで、今回も楽しかったです。ありがとうございました。またよろしくお願いします :)