あなたのスキルで飯は食えるか 史上最大のコーディングスキル判定

 あなたのスキルで飯は食えるか 史上最大のコーディングスキル判定
 http://www.itmedia.co.jp/enterprise/articles/1004/03/news002.html

 この手の問題を解くのはCマガ電脳倶楽部以来だったので、久々に楽しんで解けました。
 コーディング完了までは1時間半。テストで九蓮宝燈が正しく出力されないところで、ようやく実装ミスに気づきましたorz トータルタイム 2時間半。40分で解くってやっぱり凄いな……。

 特に工夫点はないのですが、九蓮宝燈が正しく判定されない地点では刻子/順子判定は最初に見つかったものをreturnする実装になっていたのですが、それだと特定パターンしか見つけられないことに気づいたため、offsetでズラして探していくようにして、その過程で見つかったものをyieldで返すようにしておきました。おかげで、修正点は少なくて済んだのがよかった。

 とりあえず、九蓮宝燈の結果だけ掲載しておきます。


/home/kirika/ruby% ruby marjang.rb 1112345678999
(111)(234)(567)(999)[8]
(111)(234)(678)(999)[5]
(111)(345)(678)(999)[2]
(111)(234)(567)(99)[89]
(111)(234)(789)(99)[56]
(111)(456)(789)(99)[23]
(11)(123)(456)(999)[78]
(11)(123)(678)(999)[45]
(11)(345)(678)(999)[12]
(11)(123)(456)(789)[99]
(123)(456)(789)(99)[11]

#!/usr/bin/ruby

class Marjang
  # 与えられた配牌から初めに見つかった刻子を取り出す
  # 見つかったらyieldで返す
  def kohtsu!(haipai)
    haipai_bak = haipai.dup
    offset = 0
    # 刻子判定用配列とそれ以外の牌を格納する配列
    k = []
    l = []
    while !haipai.empty?
      pai = haipai.shift
      if k.empty?
        # 刻子判定用配列が空なら、無条件で追加
        k.push(pai)
      elsif k[0] == pai
        # 刻子判定用配列に入っている値と同じなら、追加
        # 判定用配列の長さが3になった地点で、値を戻す
        k.push(pai)
        if k.length == 3
          haipai.each{|p|
            l.push(p)
          }
          yield k, l
          haipai = haipai_bak.dup
          k = []
          l = []
          offset = offset + 1
          offset.times{|n|
            l.push(haipai.shift)
          }
        end
      else
        # 上記以外の場合は、刻子ではないため、
        # 刻子判定用配列を含めて元に戻す
        k.each{|p|
          l.push(p)
        }
        k = []
        k.push(pai)
      end
    end
  end

  # 与えられた配牌から初めに見つかった順子を取り出す
  # 見つかったものはyieldで返す
  def syuntsu!(haipai)
    # 順子判定用配列と、それ以外の配列
    haipai_bak = haipai.dup
    offset = 0
    s = []
    l = []
    while !haipai.empty?
      pai = haipai.shift
      if s.empty?
        # 順子判定用配列が空なら、無条件で追加
        s.push(pai)
      elsif s[-1] + 1 > pai
        # 順子判定用配列に入っている末尾の値 + 1より小さい場合は
        # 次の牌を読む
        l.push(pai)
      elsif s[-1] + 1 == pai
        # 順子判定用配列に入っている末尾の値 + 1と等しい場合は
        # 順子判定を継続する。判定配列の長さが3になったら、
        # 順子判定用配列と残りを返す
        s.push(pai)
        if s.length == 3
          haipai.each{|p|
            l.push(p)
          }
          yield s, l
          # 配牌を元に戻して、offsetをずらす
          haipai = haipai_bak.dup
          s = []
          l = []
          offset = offset + 1
          offset.times{|n|
            l.push(haipai.shift)
          }
        end
      else
        # 上記以外の場合は、順子ではないため、
        # 順子判定用配列を含めて元に戻して新しい値をセットする
        haipai.unshift(pai)
        l.push(s.shift)
      end
    end
  end

  # 与えられた配牌から初めに見つかった対子を取り出す,なければnilを返す
  def toitsu!(haipai)
    # 対子判定用配列と、それ以外を格納する配列
    t = []
    l = []
    while !haipai.empty?
      pai = haipai.shift
      if t.empty?
        # 対子用判定配列が空なら無条件に追加
        t.push(pai)
      elsif t[0] == pai
        # 対子判定配列と同じ値が入って入れば対子確定
        t.push(pai)
        haipai.each{|p|
          l.push(p)
        }
        return t, l
      else
        # それ以外の値の場合は対子ではないので判定配列を
        # 空にして新しい値をセットする
        t.each{|p|
          l.push(p)
        }
        t = []
        t.push(pai)
      end
    end
    # 何も見つからない場合はnilと配牌をreturnする
    return nil, haipai
  end

  # 待ちを探索する
  def search_wait_pai(haipai, answer_stack)
    if haipai.length <= 4
      # 配牌4個以下になったら待ちを判定する
      # 刻子、順子が存在する場合は、単騎待ち
      kohtsu!(haipai.dup){|group, left|
        make_answer(answer_stack.dup.push(group), [left])
      }
      syuntsu!(haipai.dup){|group, left|
        make_answer(answer_stack.dup.push(group), [left])
      }
      # 対子が存在する場合
      # カンチャン/リャンメン/ペンチャン待ち
      # もしくはシャンポン待ちが存在すればよい
      group, left = toitsu!(haipai.dup)
      unless group.nil?
        if left[0] == left[-1]
          #シャンポン待ちのため、groupおよびleftが待ち
          make_answer(answer_stack.dup.push(group), left)
          make_answer(answer_stack.dup.push(left), group)
        elsif left[0] + 1 == left[1] || left[0] + 2 == left[1]
          # カンチャン/リャンメン待ち
          make_answer(answer_stack.push(group), left)
        end
      end
    else
      # それ以外の場合は、順子、刻子のどれかがないかを探す
      kohtsu!(haipai.dup){|group, left|
        search_wait_pai(left.dup, answer_stack.dup.push(group))
      }
      syuntsu!(haipai.dup){|group, left|
        search_wait_pai(left.dup, answer_stack.dup.push(group))
      }
    end
  end

  def make_answer(answer_stack, wait_group)
    if @answer[answer_stack.sort] == true
      return
    else
      @answer[answer_stack.sort] = true
    end
    answer_stack.sort.each{|group|
      print "(#{group})"
    }
    print "[#{wait_group}]\n"
  end

  def initialize
    @answer = {}
  end
end

def main
  mj = Marjang.new
  haipai = ARGV[0]
  haipai_array = []
  for i in 0...haipai.length
    haipai_array.push(haipai[i].chr.to_i)
  end
  mj.search_wait_pai(haipai_array.sort.dup, [])
end

main()