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

鍋谷さんが第3回の参考問題を出してくださったので、さっそく解きました。

今回は、野球です!

野球のボールカウント・アウトカウントの遷移を計算する。(得点・ランナー・イニング の計算は不要)
ただし、ストライク・ボール・ファウル・ヒット・ピッチャーフライしかない。
細かいルールは下記の通り:

  • ストライクが3つになったらアウトが増え、ストライクとボールがゼロになる。
  • ボールが4つになったらフォアボールになり、ストライクとボールがゼロになる。アウトは増えない。
  • ヒットを打ったらストライクとボールがゼロになる。アウトは増えない。
  • ピッチャーフライを打ったらストライクとボールがゼロになり、アウトが増える。
  • アウトが3つになったら、アウト・ストライク・ボール全てゼロになる。
  • ファウルの場合、もともとストライクが1以下の場合はストライクが増え、ストライクが2の場合には変化なし。
  • 入力は "sbsfbhsshssbbffbbssbs" のように、ひとつながりの文字列として与えられる。
  • s, b, f, h, p がそれぞれ ストライク、ボール、ファウル、ヒット、ピッチャーフライ を意味する。
  • 出力は、アウト・ストライク・ボールの順にカウントをつなげたものをコンマで区切る。例を参照。
  • 不正入力には対処しなくてよい。
  • 最終回を超えることも考慮しなくてよい。
オフラインリアルタイムどう書く第三回の参考問題 - Qiita

面白いですねー。

自分の解答

Javaで解きました。

方針は、こんな感じです。

  • 出力となる現在の状態(アウト・ストライク・ボール)をContextクラスで保持する
  • 入力のストライク、ボール、ファウル、ヒット、ピッチャーフライをそれぞれクラスで表現し、Playインタフェースを実装させる
  • Play#evaluateで、各入力に応じた処理を行う
  • Playのデフォルト実装クラスDefaultPlayを、ストライク、ボール、ファウル、ヒット、ピッチャーフライに拡張させる
  • DefaultPlay#checkで、スリーアウトをチェックする(どの入力の時も行うため、このようにしました間違っていました。スリーアウトをチェックすべきは、ストライクとピッチャーフライの時だけでした
class Context {
	int out;
	int strike;
	int ball;

	@Override
	// アウト・ストライク・ボールの順にカウントをつなげたものをコンマで区切る。
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(String.valueOf(out));
		sb.append(String.valueOf(strike));
		sb.append(String.valueOf(ball));
		return new String(sb);
	}
}

interface Play {
	void evaluate(Context con);

	void check(Context con);
}

class DefaultPlay implements Play {
	@Override
	public void evaluate(Context con) {
	}

	@Override
	// アウトが3つになったら、アウト・ストライク・ボール全てゼロになる。
	public void check(Context con) {
		if (con.out == 3) {
			con.out = 0;
			con.strike = 0;
			con.ball = 0;
		}
	}
}

class Strike extends DefaultPlay implements Play {
	@Override
	// ストライクが3つになったらアウトが増え、ストライクとボールがゼロになる。
	public void evaluate(Context con) {
		con.strike++;
		if (2 < con.strike) {
			con.out++;
			con.strike = 0;
			con.ball = 0;
		}
	}
}

class Ball extends DefaultPlay implements Play {
	@Override
	// ボールが4つになったらフォアボールになり、ストライクとボールがゼロになる。アウトは増えない。
	public void evaluate(Context con) {
		con.ball++;
		if (3 < con.ball) {
			con.strike = 0;
			con.ball = 0;
		}
	}
}

class Foul extends DefaultPlay implements Play {
	@Override
	// ファウルの場合、もともとストライクが1以下の場合はストライクが増え、ストライクが2の場合には変化なし。
	public void evaluate(Context con) {
		if (con.strike < 2) {
			con.strike++;
		}
	}
}

class Hit extends DefaultPlay implements Play {
	@Override
	// ヒットを打ったらストライクとボールがゼロになる。アウトは増えない。
	public void evaluate(Context con) {
		con.strike = 0;
		con.ball = 0;
	}
}

class PitcherFly extends DefaultPlay implements Play {
	@Override
	// ピッチャーフライを打ったらストライクとボールがゼロになり、アウトが増える。
	public void evaluate(Context con) {
		con.strike = 0;
		con.ball = 0;
		con.out++;
	}
}

/**
 * 問題: http://qiita.com/items/ebd8a56b41711ba459f9
 */
public class BallCount {

	protected Play convertPlay(char c) {
		Play result = null;
		if (c == 's') {
			result = new Strike();
		} else if (c == 'b') {
			result = new Ball();
		} else if (c == 'f') {
			result = new Foul();
		} else if (c == 'h') {
			result = new Hit();
		} else if (c == 'p') {
			result = new PitcherFly();
		}
		return result;
	}

	protected String execute(String input) {
		Context con = new Context();
		String[] tmp = new String[input.length()];
		for (int i = 0; i < input.length(); i++) {
			char c = input.charAt(i);
			Play play = convertPlay(c);
			play.evaluate(con);
			play.check(con);
			tmp[i] = con.toString();
		}

		StringBuilder result = new StringBuilder();
		result.append(tmp[0]);
		for (int i = 1; i < input.length(); i++) {
			result.append(',');
			result.append(tmp[i]);
		}
		return new String(result);
	}

	public static void main(String[] args) {
		BallCount count = new BallCount();
		System.out.println(count.execute("ssffpffssp"));
		// 010,020,020,020,100,110,120,200,210,000
		// すべてのテストは、https://gist.github.com/3380648を参照。
	}
}

クラス8個+インタフェース1個はさすがにひどいと思いましたが、後に引けませんでした。

スマートな方法は、ほかの方の解答例でお楽しみください!(^^;