「あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定」
[西尾泰三,ITmedia] 2010年04月03日 00時00分 更新
前回の記事「麻雀の待ち判定問題」の続きです。
バグがあったということで、リベンジしました。今度こそ完成!今回は、「単騎待ち」や値が重複するのを踏まえて設計をし直しました。
以前のバージョンから、さらに1時間半は使いました。
なお、前回と同じくチートイツとフリテンは考えていません。
大体の時間配分
0:00-0:30 アルゴリズム※1を考えた
0:30-1:50 コーディング&デバッグ
1:50-2:20
単騎の事を考えおらず、アルゴリズム※2練り直し
2:20-3:00 コーディング&デバッグ
3:00-3:30 提示された4種のサンプルは通ったが、
「1112223334445」のように被る場合のことを考えておらずアルゴリズム※3を見直す
3:30-4:30 コーディング&デバッグ
※1 効率化を考え、「ソート後に並んだ2つ(待ち碑と仮定)を取り出してから、チェックしていけばいいんじゃないか」といったもの
※2 頭が出来ていない場合などを考えていなかった。2つ取り出す手法の破錠したので、その場の考えで修正案を。
※3 左端から出来ているのを調べる方法では、被る場合に対処できない。結局、正直に3つの塊を作れるかという考えていくことにした。
反省点
コードを書きながら、アルゴリズムを考えるのではなく、
アルゴリズムを考えてからコードを書いてく手法はよかったが、
考えたアルゴリズムが穴だらけだった。
工夫点は、関数や変数名をわかりやすい名前にしたとか。やっていることが見てわかるような感じにした。
追記・isNormal関数を縮めた。
追記:2011年7月10日
satoshii様のご指摘でソースコードの貼付けにミスがあるのに気が付きました。ありがとうございます!!
オリジナルのソースコードは、「ダウンロード」から
改めて見ると、単騎待ちのチェックの取ってつけた感がすごい。
でも時間制限があるし、しょうがないようね!うん!
import java.awt.Graphics; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.awt.image.MemoryImageSource; import java.awt.image.Raster; import java.util.ArrayList; import java.util.Arrays; public class Mahjong2 { static BufferedImage repairBufferedImage(BufferedImage image) { Raster raster = image.getData(); int height = raster.getHeight(); int width = raster.getWidth(); int datasize = width * height; final int GRAYSIZE = 256; int[] color = new int[4]; byte[] pallet_grayscale = new byte[GRAYSIZE]; for(int i=0;i<GRAYSIZE;i++) { pallet_grayscale[i] = (byte)i; } IndexColorModel icm = new IndexColorModel(8,GRAYSIZE,pallet_grayscale,pallet_grayscale,pallet_grayscale); byte[] indexout = new byte[datasize]; int i = 0; for(int y=0;y<height;y++) { for(int x=0;x<width;x++) { raster.getPixel(x,y,color); indexout[i] = (byte)color[0]; i++; } } BufferedImage out = new BufferedImage(width,height,BufferedImage.TYPE_BYTE_INDEXED,icm); Graphics g = out.createGraphics(); g.drawImage(Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(width,height,icm,indexout,0,width)),0,0,null); g.dispose(); g = null; return(out); } public static void main(String[] args) { String tiles = "1112345678999"; String[] readyhands = Mahjong2.getReadyHands(tiles); for (int i = 0; i < readyhands.length; i++) { System.out.println(readyhands[i]); } } /** * 麻雀の手牌が入力として与えられたとき「待ち」を出力する * * @param tiles * 入力の麻雀の手牌 * @return */ static public String[] getReadyHands(String tiles) { if (!isNormal(tiles)) { return (new String[0]); } char[] data = getSortingCharacters(tiles).toCharArray(); ArrayList out = new ArrayList(); // 隣あった2つが待ちの状態と考えて抜く char[] machi = new char[2]; char[] buffer = new char[data.length - 2]; for (int i = 0; i < data.length - 1; i++) { machi[0] = data[i]; machi[1] = data[i + 1]; if (!isMachi(machi)) { continue; } for (int j = 0, k = 0; j < buffer.length; k++) { if ((i == k) || (i + 1 == k)) { continue; } buffer[j] = data[k]; j++; } String[] memo = new String[4]; checkMati(buffer, machi, memo, out, 0); } String[] dst = new String[out.size()]; for (int i = 0; i < out.size(); i++) { dst[i] = out.get(i); } return (getSortStrings(dst)); } /** * 待ち状態を調べる * * @param tiles * @param machi * (これがかぶったときは動作しなくてもいいと思う(。・ω・。)) * @param memo * @param out * 存在する待ちをここに代入する */ static private void checkMati(char[] tiles, char[] machi, String[] memo, ArrayList out, int depth) { char[] tripleset = new char[3]; char[] nokori; for (int i = 0; i < 2; i++) { if (i == 0) { nokori = getRemoveSheungFast(tiles, tripleset, 0); } else { nokori = getRemovePongFast(tiles, tripleset); } if (nokori != null) { memo[depth] = getString(tripleset); if (nokori.length == 2) { if (isAtama(nokori)) { memo[depth + 1] = getString(nokori); String text = getOutput(memo, getString(machi)); if (!out.contains(text)) { out.add(text); } } // 単騎(4枚で調べ直す) else { String force = getSortingCharacters(getString(nokori) + getString(machi)); char[] data = force.toCharArray(); for (int k = 0; k < 3; k++) { if (k == 0) { nokori = getRemoveSheungFast(data, tripleset, 0); } else if (k == 1) { nokori = getRemoveSheungFast(data, tripleset, 1); } else { nokori = getRemovePongFast(data, tripleset); } if (nokori != null) { memo[depth + 1] = getString(tripleset); String text = getOutput(memo, getString(nokori)); if (!out.contains(text)) { out.add(text); } } } } } else { checkMati(nokori, machi, memo, out, depth + 1); } } } } /** * 表示用 * * @param in1 * 順子・刻子・アタマ * @param in2 * 待ち * @return */ static private String getOutput(String[] in1, String in2) { String[] sort = getSortStrings(in1); String text = ""; int j; for (j = 0; j < sort.length; j++) { text = text.concat("(" + sort[j] + ")"); } text = text.concat("[" + in2 + "]"); return (text); } /** * ソートして返す * * @param in * @return */ static private String[] getSortStrings(String[] in) { String[] out = new String[in.length]; System.arraycopy(in, 0, out, 0, in.length); Arrays.sort(out); return (out); } /** * 「待ち」に成りえるかどうか調べる。 * * @param tiles * @return */ static private boolean isMachi(char[] tiles) { if (tiles.length == 2) { if (Math.abs(tiles[1] - tiles[0]) <= 2) { return (true); } } return (false); } /** * 「頭」に成りえるかどうか調べる。 * * @param tiles * @return */ static private boolean isAtama(char[] tiles) { if (tiles.length == 2) { if (tiles[0] == tiles[1]) { return (true); } } return (false); } /** * 「順子」があるか調べる。あったら消す。 * * @param tiles * @param shuntsu * 消した順子 * @param x * 見つかっても無視する回数 * @return 順子をぬいたもの */ static private char[] getRemoveSheungFast(char[] tiles, char[] shuntsu, int x) { int[] tiletable = new int[9]; for (int i = 0; i < tiles.length; i++) { tiletable[tiles[i] - '1']++; } boolean isshuntsu = false; for (int i = 0, j = 0; i = 1) && (tiletable[i + 1] >= 1) && (tiletable[i + 2] >= 1)) { if (j < x) { j++; continue; } shuntsu[0] = (char) (i + '1'); shuntsu[1] = (char) (i + '2'); shuntsu[2] = (char) (i + '3'); tiletable[i + 0]--; tiletable[i + 1]--; tiletable[i + 2]--; isshuntsu = true; break; } } if (!isshuntsu) { return (null); } return (getCharacters(tiletable)); } /** * 「刻子」があるか調べる。あったら消す。 * * @param tiles * @param kotsu * 消した刻子 * @return 刻子をぬいたもの */ static private char[] getRemovePongFast(char[] tiles, char[] kotsu) { int[] tiletable = new int[9]; for (int i = 0; i < tiles.length; i++) { tiletable[tiles[i] - '1']++; } int ispong = -1; for (int i = 0; i = 3) { ispong = i; tiletable[i] -= 3; break; } } if (ispong == -1) { return (null); } for (int i = 0; i < 3; i++) { kotsu[i] = (char) (ispong + '1'); } return (getCharacters(tiletable)); } /** * テーブル(9種の碑がそれぞれいくつあるか)から文字配列に変換します。 * * @param characters * @return */ static private char[] getCharacters(int[] tiletable) { int sum = 0; for (int i = 0; i < tiletable.length; i++) { sum += tiletable[i]; } char[] out = new char[sum]; for (int i = 0, k = 0; i < tiletable.length; i++) { for (int j = 0; j < tiletable[i]; j++) { out[k] = (char) (i + '1'); k++; } } return (out); } /** * 文字配列を文字列に変換します。 * * @param characters * @return */ static private String getString(char[] characters) { String out = ""; for (int i = 0; i < characters.length; i++) { out = out.concat(String.valueOf(characters[i])); } return (out); } /** * 与えられた麻雀の手牌をソートする * * @param tiles * @return */ static private String getSortingCharacters(String tiles) { char[] data = tiles.toCharArray(); Arrays.sort(data); return (getString(data)); } /** * 与えられた麻雀の手牌が条件にそっているか調べる。 * * @param tiles * @return */ static private boolean isNormal(String tiles) { return ((tiles.matches("[1-9]{13}"))); } }
コメント
コンパイル通りません。
修正ありがとうございました。
他の方にも同じ指摘をしたのですが、
1111777888999
のときに
5枚目の一萬を待っています。
このときは何も出力しないのが正解のはずです。
さとしさんコメントありがとうございます。
返信遅れてすみません。
ご指摘いただいたとおり間違っていますね。
そもそも4つまでというルールがアルゴリズムに抜けているようです。