麻雀の待ち問題をやってみた

by kaikoga forked from trace("自分用") (diff: 163)
makeplex salon:あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定 (1/2) - ITmedia エンタープライズ
http://www.itmedia.co.jp/enterprise/articles/1004/03/news002.html

の問題を解いてみた
問題文を読んでから正しい答えが出せるようになるまで1時間と少し
wonderflにアップロードして体裁を整えるのにさらに15分くらい?
いろいろ面倒だったので深く考えずに正規表現でガンガン回す方針

最初は面子抽出中の手牌データをクラスを作って持とうかと思ったけど
クラスを定義するのが面倒だったので文字列操作でどうにかする方針に。
コーディングしながらJavaっぽくなるかLLっぽくなるか考えられるのが
AS3の最大の魅力でしょうか・・・

いちおう正常に動いてはいるみたいだけど結構ボロがあるみたい・・・
後から気付いた要改善点も敢えてリファクタリングはしないで
日本語のコメントで書いておくにとどめるという方針で
♥0 | Line 126 | Modified 2010-04-04 05:40:02 | MIT License
play

ActionScript3 source code

/**
 * Copyright kaikoga ( http://wonderfl.net/user/kaikoga )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/mX6V
 */

// forked from kaikoga's trace("自分用")
package {
    
    /*
     * makeplex salon:あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定 (1/2) - ITmedia エンタープライズ
     * http://www.itmedia.co.jp/enterprise/articles/1004/03/news002.html
     * 
     * の問題を解いてみた
     * 問題文を読んでから正しい答えが出せるようになるまで1時間と少し
     * wonderflにアップロードして体裁を整えるのにさらに15分くらい?
     * いろいろ面倒だったので深く考えずに正規表現でガンガン回す方針
     * 
     * 最初は面子抽出中の手牌データをクラスを作って持とうかと思ったけど
     * クラスを定義するのが面倒だったので文字列操作でどうにかする方針に。
     * コーディングしながらJavaっぽくなるかLLっぽくなるか考えられるのが
     * AS3の最大の魅力でしょうか・・・
     *
     * いちおう正常に動いてはいるみたいだけど結構ボロがあるみたい・・・
     * 後から気付いた要改善点も敢えてリファクタリングはしないで
     * 日本語のコメントで書いておくにとどめるという方針で
     */
    
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.text.TextFormat;
    
    public class FlashTest extends Sprite {
        
        // 結局使わなかった変数
        private var v:Array = [];
        
        public function FlashTest() {
            this.machiOut("1112224588899"); //(111)(222)(888)(99)[45]
            this.machiOut("1122335556799"); //(123)(123)(555)(99)[67]、(123)(123)(55)(567)[99]、(123)(123)(99)(567)[55]
            this.machiOut("1112223335559"); //(123)(123)(123)(555)[9]と(111)(222)(333)(555)[9]
            this.machiOut("1223344888999"); //4をアタマにしての[23]待ちと、1単騎、4単騎で3個の答えになります。
            this.machiOut("1112345678999"); //九蓮宝燈
        }
        
        public function machiOut(input:String):void {
            this.trace("machi(" + input + "): ");
            // いやせっかく machi() 作ったんだからそっち呼べし
            this.trace(this.doMachi(input).join(", "));
            this.trace();
        }

        public function machi(input:String):String {
            return this.doMachi(input).join(", ");
        }

        private function doMachi(input:String):Array {
            //Internal intermediate format: "14588899@(11)(222)"
            //Unorganized tiles and "@", followed by organized sets
            var intermediate:Array = [input + "@"];
            
            //Extract atama only once
            var intermediateNoAtama:Array = [];
            for each (var atama:String in ["11", "22", "33", "44", "55", "66", "77", "88", "99"]) {
                this.tryAddExtractSet(intermediate, atama, intermediateNoAtama);
            }
            
            //Extract sets many times
            for each (var koutsu:String in ["111", "222", "333", "444", "555", "666", "777", "888", "999"]) {
                this.multiTryAddExtractSet(intermediate, koutsu);
                this.multiTryAddExtractSet(intermediateNoAtama, koutsu);
            }
            for each (var shuntsu:String in ["123", "234", "345", "456", "567", "678", "789"]) {
                this.multiTryAddExtractSet(intermediate, shuntsu);
                this.multiTryAddExtractSet(intermediateNoAtama, shuntsu);
            }
            
            //intermediate = intermediate.concat(intermediateNoAtama);
            intermediate = this.filterTanki(intermediate).concat(this.filterPairMachi(intermediateNoAtama));
            var result:Array = [];
            for each (var hand:String in intermediate) {
                result.push(hand.split("@", 2)[1]);
            }
            return result.sort();
        }
        
        private function filterPairMachi(hands:Array):Array {
            //filters machi created with double tiles and extract
            var result:Array = [];
            for each (var hand:String in hands) {
                if (hand.indexOf("@") == 2) {
                    switch (hand.charCodeAt(0) - hand.charCodeAt(1)) {
                        case -2:
                        case 2:
                        case -1:
                        case 1:
                        case 0:
                        // たぶん間違い。
                        // 入力は既にソートされているので、
                        // case 1とcase 2は考慮に入れなくていい。
                        result.push(this.extractMachi(hand));
                        break;
                    }
                }
            }
            return result;
        }
        
        private function filterTanki(hands:Array):Array {
            //filters machi created with single tile and extract
            var result:Array = [];
            for each (var hand:String in hands) {
                if (hand.indexOf("@") == 1) {
                    result.push(this.extractMachi(hand));
                }
            }
            return result;
        }
        
        private function extractMachi(hand:String):String {
            //The machi must be valid, check is done in filter~ methods
            var handArray:Array = hand.split("@", 2);
            var sets:String = handArray[1];
            var machi:String = handArray[0];
            sets = (")" + sets + "(").split(")(").slice(1, -1).sort().join(")(");
            sets = "(" + sets + ")";
            return "@" + sets + "[" + machi + "]";
        }
        
        private function multiTryAddExtractSet(hands:Array, tiles:String, target:Array = null):void {
            //Simple loop without thinking
            // tryAddExtractSet()の挙動のせいで
            // 実は4回繰り返す必要はなかった
            for (var i:int = 0; i < 4; i++) {
                if (!this.tryAddExtractSet(hands, tiles, target)) {
                    break;
                }
            }
        }
        
        private function tryAddExtractSet(hands:Array, tiles:String, target:Array = null):Boolean {
            //Add to array if tryExtractSet() succeeds with unique result
            // 実は(hands == target)の場合のみ面子の抽出が複数回行われるらしい
            // 半ばウラ技じみた挙動だったけどたまたま動いてたらしい
            // たぶん新しく生成された手牌データを一旦別の配列に保存しておく
            // (再処理しない)のが当初意図していた挙動だと思う
            target ||= hands;
            var success:Boolean = false;
            var regExp:RegExp = new RegExp("(.*)" + tiles.split("").join("(.*)") + "(.*)@(.*)");
            //trace("Main.tryAddExtractSet(): RegExp: ", regExp.source);
            for each (var hand:String in hands) {
                var newHand:String = tryExtractSet(hand, tiles, regExp);
                if (newHand && hands.indexOf(newHand) < 0) {
                    target.push(newHand);
                    success = true;
                }
            }
            return success;
        }
        
        private function tryExtractSet(hand:String, tiles:String, regExp:RegExp):String {
            //Extracts set from unorganized section and append to organized section
            var tileCount:int = tiles.length;
            if (hand.indexOf("@") < tileCount) {
                return null;
            }
            var exec:Object = regExp.exec(hand);
            if (!exec) {
                return null;
            }
            var result:String = "";
            for (var i:int = 1; i <= tileCount + 1; i++) {
                result += exec[i];
            }
            result += "@" + exec[i] + "(" + tiles + ")";
            //trace("Main.tryExtractSet(): Extraction: ", hand, "->", tiles, "=>", result, "//", exec)
            return result;
        }
        
        
        private var _traceField:TextField;
        public function trace(...message):void {
            if (!this._traceField) {
                this._traceField = new TextField();
                this._traceField.width = this.stage.stageWidth;
                this._traceField.height = this.stage.stageHeight;
                this._traceField.defaultTextFormat = new TextFormat("_typewriter", 10);
                this._traceField.wordWrap = true;
                this.addChild(this._traceField);
            }
            this._traceField.appendText(message.join(" ") + "\n");
        }
        
    }
}

Forked