Enumを使って処理を分ける(c.f.第3回オフラインリアルタイムどう書くの参考問題)
第9回のどう書くの問題のおかげで気づきましたが、EnumのstaticメソッドのvalueOfはベンリですね!
文字を与えてEnumの値を取ってくるだけの基本的なメソッドです。なにをいまさらという感じですが……。
このメソッドを使えば、入力文字(char)による処理の振り分けがカンタンに書けます。
enum Foo { A { @Override void print() { System.out.println("Aです"); } }, B { @Override void print() { System.out.println("Bです"); } }, C { @Override void print() { System.out.println("Cです"); } }; abstract void print(); } public class Main { public static void main(String[] args) { Foo.valueOf("A").print(); } }
Aです
こんな感じに。
なので、たとえば、第3回の参考問題なども楽勝になります。
野球のボールカウント・アウトカウントの遷移を計算する。(得点・ランナー・イニング の計算は不要)
ただし、ストライク・ボール・ファウル・ヒット・ピッチャーフライしかない。
細かいルールは下記の通り:
- ストライクが3つになったらアウトが増え、ストライクとボールがゼロになる。
- ボールが4つになったらフォアボールになり、ストライクとボールがゼロになる。アウトは増えない。
- ヒットを打ったらストライクとボールがゼロになる。アウトは増えない。
- ピッチャーフライを打ったらストライクとボールがゼロになり、アウトが増える。
- アウトが3つになったら、アウト・ストライク・ボール全てゼロになる。
- ファウルの場合、もともとストライクが1以下の場合はストライクが増え、ストライクが2の場合には変化なし。
- 入力は "sbsfbhsshssbbffbbssbs" のように、ひとつながりの文字列として与えられる。
- s, b, f, h, p がそれぞれ ストライク、ボール、ファウル、ヒット、ピッチャーフライ を意味する。
(後略)
オフラインリアルタイムどう書く第三回の参考問題
sbfhpの各文字に対応するメソッドを定義して直接呼び出したいなーと悩んでいましたが、この程度ならEnumでできるわけです。
- 最初に書いた解答 http://d.hatena.ne.jp/torazuka/20120818/yhpg
- リフレクション編 http://d.hatena.ne.jp/torazuka/20120818/reflection
上記のようなことをする必要はありませんでした……。
書き直したら、結構短くなりました。
enum Rule { s { @Override int[] play(int[] count) { count[1] += 1; if (count[1] == 3) { count[0] += 1; count[1] = 0; } return count; } }, b { @Override int[] play(int[] count) { if ((count[2] += 1) == 4) { count[1] = 0; count[2] = 0; } return count; } }, f { @Override int[] play(int[] count) { if (count[1] < 2) { count[1] += 1; } return count; } }, h { @Override int[] play(int[] count) { count[1] = 0; count[2] = 0; return count; } }, p { @Override int[] play(int[] count) { count[0] += 1; count[1] = 0; count[2] = 0; return count; } }; abstract int[] play(int[] count); } /** * 問題: http://qiita.com/items/ebd8a56b41711ba459f9 */ public class BallCountEx { public String solve(String input) { int[] count = new int[3]; // out, strike, ball char[] charArray = input.toCharArray(); String result = ""; for (char each : charArray) { Rule rule = Rule.valueOf(String.valueOf(each)); count = rule.play(count); count = outCheck(count); for (int i : count) { result += i; } result += ","; } return result.substring(0, result.length() - 1); } private int[] outCheck(int[] count) { if (count[0] == 3) { count[0] = 0; count[1] = 0; count[2] = 0; } return count; } }
- 上のコードと、テストコード https://gist.github.com/torazuka/5329238
めでたし、めでたし。
「第9回 オフラインリアルタイムどう書く」別解(Java)
「第9回 オフラインリアルタイムどう書く」へなちょこ解答(Java)(http://d.hatena.ne.jp/torazuka/20130407/yhpg)の別解を書きました。
以前はどう書くの問題を見た瞬間に、あまり何も考えずに個々の要素を表わすクラスを作り始めていたのですが、最近そういうやり方をしなくなりました。
そこで試しに、昔のやり方で昨日の解答を書き改めてみました。
例のバス代の問題です。
解答
import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; class Paseenger { Age age; Kind kind; public Paseenger(final String str) { age = createAge(str.charAt(0)); kind = createKind(str.charAt(1)); } private Age createAge(final char c) { AgeKey key = AgeKey.valueOf(String.valueOf(c)); return key.make(); } private Kind createKind(final char c) { KindKey key = KindKey.valueOf(String.valueOf(c)); return key.make(); } public void lineUp(final BusfeeEx bf) { age.lineUp(this, bf); } public int getFee(final int base) { int tmp = age.getFee(base); return kind.discount(tmp); } } abstract class Age { abstract int getFee(final int base); abstract void lineUp(final Paseenger p, final BusfeeEx bf); } class Adult extends Age { @Override public int getFee(final int base) { return base; } @Override void lineUp(final Paseenger p, final BusfeeEx bf) { bf.pushPassenger(this, p); } } class Child extends Age { @Override public int getFee(final int base) { return BusfeeEx.getHalf(base); } @Override void lineUp(final Paseenger p, final BusfeeEx bf) { bf.pushPassenger(this, p); } } class Infant extends Age { @Override public int getFee(final int base) { return BusfeeEx.getHalf(base); } @Override void lineUp(final Paseenger p, final BusfeeEx bf) { p.kind.lineUp(p, bf); } } enum AgeKey { A { @Override Age make() { return new Adult(); } }, C { @Override Age make() { return new Child(); } }, I { @Override Age make() { return new Infant(); } }; abstract Age make(); } abstract class Kind { abstract int discount(final int original); abstract void lineUp(final Paseenger p, final BusfeeEx bf); } class Normal extends Kind { @Override public int discount(final int original) { return original; } @Override void lineUp(final Paseenger p, final BusfeeEx bf) { bf.pushPassenger(this, p); } } class Pass extends Kind { @Override public int discount(final int original) { return 0; } @Override void lineUp(final Paseenger p, final BusfeeEx bf) { bf.pushPassenger(this, p); } } class Welfare extends Kind { @Override public int discount(final int original) { return BusfeeEx.getHalf(original); } @Override void lineUp(final Paseenger p, final BusfeeEx bf) { bf.pushPassenger(this, p); } } enum KindKey { n { @Override Kind make() { return new Normal(); } }, p { @Override Kind make() { return new Pass(); } }, w { @Override Kind make() { return new Welfare(); } }; abstract Kind make(); } /** * 問題: http://nabetani.sakura.ne.jp/hena/ord9busfare/ */ public class BusfeeEx { List<Paseenger> adults = new ArrayList<>(); List<Paseenger> children = new ArrayList<>(); List<Paseenger> infantNormal = new ArrayList<>(); List<Paseenger> infantPass = new ArrayList<>(); List<Paseenger> infantWelfare = new ArrayList<>(); static int getHalf(final int original) { int tmp = original / 2; if (tmp % 10 == 0) { return tmp; } return tmp + (10 - tmp % 10); } List<Paseenger> createPassengers(final String str) { List<String> tmp = Arrays.asList(str.split(",")); List<Paseenger> result = new ArrayList<>(); for (String each : tmp) { result.add(new Paseenger(each)); } return result; } public void pushPassenger(final Adult age, final Paseenger p) { adults.add(p); } public void pushPassenger(final Child age, final Paseenger p) { children.add(p); } public void pushPassenger(final Normal kind, final Paseenger p) { infantNormal.add(p); } public void pushPassenger(final Pass kind, final Paseenger p) { infantPass.add(p); } public void pushPassenger(final Welfare kind, final Paseenger p) { infantWelfare.add(p); } public String solve(final String input) { int base = Integer.valueOf(input.split(":")[0]); List<Paseenger> passengers = createPassengers(input.split(":")[1]); for (Paseenger each : passengers) { each.lineUp(this); } int freeLimit = adults.size() * 2; freeLimit = discount(infantNormal, freeLimit); discount(infantWelfare, freeLimit); passengers.clear(); passengers.addAll(adults); passengers.addAll(children); passengers.addAll(infantNormal); passengers.addAll(infantPass); passengers.addAll(infantWelfare); int result = 0; for (Paseenger each : passengers) { result += each.getFee(base); } return String.valueOf(result); } private int discount(List<Paseenger> list, final int freeLimit) { int result = freeLimit; Iterator<Paseenger> iterator = list.iterator(); while (iterator.hasNext()) { iterator.next(); if (0 < result) { iterator.remove(); result--; } } return result; } }
- 上のコードと、テストコード https://gist.github.com/torazuka/5328992
結論: アカン…
ちなみに、ダブルディスパッチは、結城先生のデザパタ本のVisitor章で理解したつもりになっていますが、おかしい等のツッコミありましたらよろしくお願いします。
「第9回 オフラインリアルタイムどう書く」へなちょこ解答(Java)
- オフラインリアルタイムどう書くとは
- 鍋谷さんが出してくださるお題を、参加者が好きなプログラミング言語で解いて楽しむ会です。
春の嵐が来る中、オフラインリアルタイムどう書くが強行されました。
今回は、バス代です!
- バス代 横へな 2013.4.6 http://nabetani.sakura.ne.jp/hena/ord9busfare/
「今までで一番カンタンだった」という方もいる中、自分は時間内に解けませんでした。6つくらいテストケースが通らなかった……(定期券を持つ幼児を考慮し忘れていたのが敗因)。2連敗なので次は何とかしたいところです。
今回は、オフラインリアルタイムどう書く史上初めて、参加者の言語がすべてバラバラでした。主催の鍋谷さんを含めて7人参加で、C++、Haskell、Java、Objective-C、Perl、Python、Ruby(アルファベット順)が使用されました。発表も面白かったです。
自分の解答(Java)
(また帰りの電車の中でデバッグする羽目に)
import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * 問題: http://nabetani.sakura.ne.jp/hena/ord9busfare/ */ public class Busfee { private final boolean isAdult(final String string) { return string.charAt(0) == 'A'; } private final boolean isInfant(final String string) { return string.charAt(0) == 'I'; } private final boolean hasPass(final String string) { return string.charAt(1) == 'p'; } private final boolean hasWelfare(final String string) { return string.charAt(1) == 'w'; } public String solve(final String input) { int base = Integer.valueOf(input.split(":")[0]); String passenger = input.split(":")[1]; List<String> passengers = Arrays.asList(passenger.split(",")); int adultNum = countAdult(passengers); int result = 0; int freeNum = 0; List<String> welfareInfants = new ArrayList<>(); for (int i = 0; i < passengers.size(); i++) { String one = passengers.get(i); if (isInfant(one) && freeNum < adultNum * 2) { if (hasPass(one)) { continue; } if (hasWelfare(one)) { welfareInfants.add(one); continue; } freeNum++; continue; } result += getFee(one, base); } for (String each : welfareInfants) { if (freeNum < adultNum * 2) { freeNum++; continue; } result += getFee(each, base); } return String.valueOf(result); } private int getFee(final String target, final int base) { int tmp = 0; if (isAdult(target)) { tmp = base; } else { tmp = getHalf(base); } return discount(target, tmp); } private int discount(final String target, final int original) { if (hasPass(target)) { return 0; } else if (hasWelfare(target)) { return getHalf(original); } return original; } private int getHalf(final int n) { int tmp = n / 2; if (tmp % 10 == 0) { return tmp; } return tmp + (10 - tmp % 10); } private int countAdult(final List<String> passengers) { int result = 0; for (String each : passengers) { if (isAdult(each)) { result++; } } return result; } }
- 上のコードと、テストコード https://gist.github.com/torazuka/5186764
考え方
乗客が幼児であった場合、無料枠(大人の人数×2)に収まる人数分は、無料になります。
その際、福祉割引が適用される幼児(通常の幼児料金のさらに半額、つまり、大人の4分の1の額になる)よりも、福祉割引が適用されない幼児を優先して無料枠に入れてあげた方が、全体の料金は安くなります。
というわけで、福祉割引が適用される幼児がいた場合は、無料適用を後回しにします。
修正点メモ
会場で書いたコードからどこを直したか。
- 「半額にする、ただし端数は切り上げる」の実装を間違えていたので直した
- ついでに、メソッドに切り出した
- 不要な条件分岐をなくした
- 幼児が0、または大人が0のとき・・・などで処理を分けるのをやめた
- ADULT == 'A'、INFANT == 'I'等の定数定義をやめて、メソッドにした
- i番目の乗客の年齢区分をageArray[i]に、割引種別をkindArray[i]に格納していたが、Listに乗客をそのまま入れることにした
1と2は、発表時に指摘いただきました。
3と4は必須の修正ではありませんでしたが、たとえば、if文の条件式で乗客が幼児かどうかを判定するとき、「リスト要素i番目の乗客の1文字目は'I'か」などと書くよりも、「isInfant(one)」とした方が何をしたいかが分かりやすい気がしたので、変更しました。
そんな感じです。
今回も楽しかったです。ありがとうございました。