解いてみた

ITmedia「あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定」ってのを解いてみた。
馬鹿正直なコードすぎて恥ずかしい。
もっとかっこいいコードが書きたい。
あとバグ出したんで3時間以上かかってます。
同じ牌が4枚しかないこと、チートイツについては考慮してません。


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MachiChecker {
public static void main(String[] args) {
//標準入力を読んで空改行じゃなければ待ちチェック
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
System.in));
while (true) {
String line = reader.readLine();
if (line != null && line.length() > 0) {
showMachi(line);
} else {
System.exit(0);
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}

public static void showMachi(String str) {
//入力をソートして配列化。13番目の要素(待ち)は0とする
final int[] pais = new int[14];
for (int i = 0; i < 13; i++) {
pais[i] = Integer.parseInt(str.substring(i, i + 1));
}
pais[13] = 99;
Arrays.sort(pais);
pais[13] = 0;

//順子と刻子をすべて取り出して、ソート
List paiSets = findJuntu(pais);
paiSets.addAll(findKoutu(pais));
Collections.sort(paiSets, new Comparator() {
@Override
public int compare(PaiSet o1, PaiSet o2) {
int val1 = pais[o1.pai1] * 100 + pais[o1.pai2] * 10 + pais[o1.pai3];
int val2 = pais[o2.pai1] * 100 + pais[o2.pai2] * 10 + pais[o2.pai3];
return val1 - val2;
}
});

//すべての頭を取り出す
List atamas = findAtama(pais);
//見つかった待ち文字列
Set found = new HashSet();

//牌のインデックスのセット
Set nums = new HashSet();
for (Atama atama : atamas) {
nums.add(atama.pai1);
nums.add(atama.pai2);
//順子、刻子の組み合わせで牌のインデックスがかぶらない物をfoundに追加
for (int i = 0; i < paiSets.size(); i++) {
PaiSet set1 = paiSets.get(i);
if (addNumbers(nums, set1)) {
for (int j = i + 1; j < paiSets.size(); j++) {
PaiSet set2 = paiSets.get(j);
if (addNumbers(nums, set2)) {
for (int k = j + 1; k < paiSets.size(); k++) {
PaiSet set3 = paiSets.get(k);
if (addNumbers(nums, set3)) {
for (int l = k + 1; l < paiSets.size(); l++) {
PaiSet set4 = paiSets.get(l);
if (addNumbers(nums, set4)) {
found.add(atama.toString(pais) + set1.toString(pais)
+ set2.toString(pais) + set3.toString(pais) + set4.toString(pais));
removeNumbers(nums, set4);
}
}
removeNumbers(nums, set3);
}
}
removeNumbers(nums, set2);
}
}
removeNumbers(nums, set1);
}
}
nums.remove(atama.pai1);
nums.remove(atama.pai2);
}

//結果表示
for (String yaku : found) {
System.out.println(yaku);
}
}

private static List findJuntu(int[] pais) {
List rtn = new ArrayList();
for (int i = 0; i < pais.length; i++) {
for (int j = i + 1; j < pais.length; j++) {
for (int k = j + 1; k < pais.length; k++) {
if ((pais[i] == pais[j] - 1 && pais[j] == pais[k] - 1)
|| (pais[i] == pais[j] - 1 && k == 13)
|| (pais[i] == pais[j] - 2 && k == 13)) {
rtn.add(new PaiSet(i, j, k));
}
}
}
}
return rtn;
}

private static List findKoutu(int[] pais) {
List rtn = new ArrayList();
for (int i = 0; i < pais.length; i++) {
for (int j = i + 1; j < pais.length; j++) {
for (int k = j + 1; k < pais.length; k++) {
if ((pais[i] == pais[k] && pais[j] == pais[k])
|| (pais[i] == pais[j] && k == 13 && j != 13)) {
rtn.add(new PaiSet(i, j, k));
}
}
}
}
return rtn;
}

private static List findAtama(int[] pais) {
List rtn = new ArrayList();
for (int i = 0; i < pais.length; i++) {
for (int j = i + 1; j < pais.length; j++) {
if (pais[i] == pais[j] || j == 13) {
rtn.add(new Atama(i, j));
}
}
}
return rtn;
}

private static boolean addNumbers(Set set, PaiSet paiSet) {
if(set.contains(paiSet.pai1) || set.contains(paiSet.pai2) || set.contains(paiSet.pai3)) {
return false;
}
set.add(paiSet.pai1);
set.add(paiSet.pai2);
set.add(paiSet.pai3);
return true;
}

private static void removeNumbers(Set set, PaiSet paiSet) {
set.remove(paiSet.pai1);
set.remove(paiSet.pai2);
set.remove(paiSet.pai3);
}
}

class Atama {
public int pai1;
public int pai2;

public Atama(int pai1, int pai2) {
this.pai1 = pai1;
this.pai2 = pai2;
}

public String toString(int[] pais) {
if(pai1 != 13 && pai2 != 13) {
return "("+ pais[pai1] + "" + pais[pai2] + ")";
} else {
return "["+ pais[pai1] + "]";
}
}
}

class PaiSet {
public int pai1;
public int pai2;
public int pai3;

public PaiSet(int pai1, int pai2, int pai3) {
this.pai1 = pai1;
this.pai2 = pai2;
this.pai3 = pai3;
}

public String toString(int[] pais) {
if(pai3 != 13) {
return "("+ pais[pai1] + "" + pais[pai2] + "" + pais[pai3] + ")";
} else {
return "["+ pais[pai1] + "" + pais[pai2] + "]";
}
}
}

結果


1112224588899
(99)(111)(222)[45](888)
1122335556799
(55)(123)(123)(567)[99]
(99)(123)(123)[55](567)
(99)(123)(123)(555)[67]
1112223335559
[9](111)(222)(333)(555)
[9](123)(123)(123)(555)
1223344888999
[4](123)(234)(888)(999)
[1](234)(234)(888)(999)
(44)(123)[23](888)(999)
1112345678999
(11)(123)[45](678)(999)
(99)(111)(234)[56](789)
[2](111)(345)(678)(999)
(99)(111)(234)(567)[89]
(11)[12](345)(678)(999)
(11)(123)(456)[78](999)
(11)(123)(456)(789)[99]
[5](111)(234)(678)(999)
(99)[11](123)(456)(789)
(99)(111)[23](456)(789)
[8](111)(234)(567)(999)

なっげー
かっこわるーい

一応解説すると、入力された数字を14要素の数値の配列にし(最後の要素はワイルドカードとして考える)、
そこからあり得る全ての頭、順子、刻子をリストアップして、牌がかぶらない組み合わせを
表示してます。
九蓮宝燈の場合、頭が19、順子と刻子が42あり、それらを全部組み合わせると
19*42*41*40*39/(1*2*3*4)=2126670
通りの組み合わせになりますが、牌がかぶる組み合わせは試さずに切り捨ててるので
(ソースのネストしまくって汚い辺り)実際のところは28676回の試行で済み、
数ミリ秒(Core2Duo 2.13GHzのMac。数回目の試行でHotSpotで最適化された後の数字)
と比較的実用レベルの速度におさまりました。

追記:ちょっとバグってたんで直しました。
さらに追記:カンチャン待ちを忘れてたんで追加しました。
さらに追記:よく見たら余計なコードがあったんで整理