すげ〜今更だけど、「史上最大のコーディングスキル判定」をやってみた。

14か月くらいいまさらだけど、気になってのでやってみた

問題は

http://www.itmedia.co.jp/enterprise/articles/1004/03/news002_2.html

 

要件が書いていないチートイツは無視で、5枚目牌を待つ待ちも許容した。(前者は割と簡単。後者もまぁできるけど、問題に定義されていないので無視。1113334447777というテンパイ形も許容)

 

テンパイ形だけ分かればいいので、最初にメンツを取れるだけとって、4つ取れたらタンキ待ち確定。3つ取った後、4つ目のメンツが取れなかったら、残りの4枚がトイツ+ターツか、トイツ2個になっていたらテンパイ形にする。

 

で、この手の問題はやっぱり再帰関数だよねということで、文法を思い出しながらocamlで試行錯誤して3時間。for文使った方が楽だと思いなおして、ロジックを整理してrubyで書き直して全テンパイ形を得られるまで2時間ちょっと。出力とか、繰り返し回数減らすのにちょっといじって1時間、ってあたり。rubyで書いたといっても、使ったのは配列の関数くらいで、ほとんどcだった。 このあたりは普段rubyでかかないので勘弁してくださいってことで、、、。


# -*- coding: utf-8 -*-
##史上最大のコーディングスキル判定
##グローバル変数の定義
$pai=[1,2,3,4,5,6,7,8,7,9,9,9,1] #牌の組み合わせ。処理前にソートされる。
$pai2=[1,1,1,1,1,1,1,1,1,1,1,1,1] #牌をとったらそのインデックスの要素は0になる。
$all=Array.new # テンパイ形の牌の組み合わせの配列を要素にもつ配列。テンパイ形が全て入る。
$len=$pai.length
##判定関数たち。引数は$paiのインデックス
def isMenzu(h1,h2,h3)
  return ($pai[h2] == $pai[h1]+1 && $pai[h3]==$pai[h2]+1) || ($pai[h1]==$pai[h2] && $pai[h1]==$pai[h3])
end
def isToizu(h1,h2)
  return $pai[h1]==$pai[h2]
end
def isTarzu(h1,h2)
  return $pai[h2]==$pai[h1]+2 || $pai[h2]==$pai[h1]+1 || $pai[h2]==$pai[h1]
end
##残り1枚になったときのタンキ待ちの組み合わせを$allに入れる
def hantei1(arr)
  for i in 0..12
    if $pai2[i]==1
      $all.push((arr +[[$pai[i],-1]]).sort)
    end
  end
end
##残り4枚になったとき、対子+ターツの形を$allに入れる。面子+タンキはhantei1で判定済みのはず。
def hantei4(arr)
  a1=Array.new
  a2=Array.new
  for k in 0..12
    if $pai2[k]==1
      a1.push(k)
    end
  end
  if a1.length==4
    0.upto(2){|i|
      (i+1).upto(3){|j|
        if isToizu(a1[i],a1[j])
          a2=a1.dup
          a2.slice!(j)
          a2.slice!(i)        ## このあたりもっときれいに書けないかなぁ。
          if isTarzu(a2[0],a2[1])
            tmp=arr.dup
            tmp.push([$pai[a1[i]],$pai[a1[j]]])
            tmp.push([$pai[a2[0]],$pai[a2[1]],-1]) # -1は、結果出力時に待ちになっている部分を判定するためのゴマカシ
            $all.push(tmp.sort) # sortしたものを入れておくと重複排除のときに簡単になる
          end
        end
      }
    }
  end
end

def make_list(ans,num,count) # ansはテンパイ形の13枚の組み合わせを作るために、3つずつ牌の組み合わせを入れる。
  if count==4 # 残り1枚
    hantei1(ans)
  else
    num.upto($len-3){|i|
      next if $pai2[i]==0
      (i+1).upto($len-2){|j|
        next if $pai2[j]==0
        (j+1).upto($len-1){|k|
          next if $pai2[k]==0
          if isMenzu(i,j,k) # 重複なしに適当に取った3枚がメンツになっているなら、次のメンツをとる。
            $pai2[i],$pai2[j],$pai2[k]=0,0,0
            make_list(ans+[[$pai[i],$pai[j],$pai[k]]],i+1,count+1)
            $pai2[i],$pai2[j],$pai2[k]=1,1,1
          end
        }
      }
    }
    if count==3 # 残り4枚からメンツを取らなかったとき
      hantei4(ans)
    end
  end
end

# main part
if $len == 13
  $pai.sort!
  make_list(Array.new,0,0)
  $all.uniq!
  # output result
  $all.each{|arr|
    arr.each{|a1|
      if (a1.index(-1)).nil?
        c1="("
        c2=")"
      else
        c1="["
        c2="]"
      end
      printf(c1)
      a1.each{|a2|
        printf("%d",a2) if a2 != -1
      }
      printf(c2)
    }
    printf("\n")
  }
end

 

こんなかんじ。ocamlで書いてたときに、for文てどうやって書くんだっけと思った時点で、rubyで書いた方が早いと思ってしまったのです、、、。make_list関数をocamlで書こうとしている間に時間切れ。結局、ループ変数を全部引数に持たせて再起関数を書けばできたような気もしつつ。結果を全部突っ込む配列を用意しないでスマートに書けたらいいんだけど、重複を排除するんだよなぁと思うと、どうしてもこんな感じ。例外処理は、、、別に変なもの入力しないから勘弁してください。 チンイツ形以外にも拡張するには、適当に数字を離して牌の名前を振れば問題なく行けるはず。マンズ 1〜9 ソーズ 21〜29 ピンズ 41〜49 字牌 60,63,66,,,, とか。役の判定を考え出すと面倒だなあ。

っつうか、ブログ書いてる間にすでに繰り返し回数減らす変更を思いついてしまって、どうしようもないな。まぁ、make_listに渡すnumが、num>4+count*3 だと一回も回らないっていう条件を入れ忘れたってこと。(その後、実際にループを減らすのをやってみましたが、そのif文を入れると、単純で時間がかからない時は早くなるけど、1111222233334みたいな、ややこしくて時間がかかる場合には遅くなることがわかった。どうしようもないね。)

なんか、gooブログでソース書こうとすると、だいぶムカつくね。もうやめようかな。

結局、自分はプログラマとしてはいまいちっぽいので、よかったというべきか悪かったというべきか、、、
まぁ、コーディングが仕事というわけじゃないし、ruby自体書くのは久しぶりなんだ、、、。

==
当時いろいろとコーディングされた物が公開されているのでみてみた。そうか、作業配列の作り方がまずかったか。配列arr[i]に、牌iの枚数をぶち込んで、枚数を勘定してその数字はメンツになれるかどうかを、一つ一つの数字ごとに判断するという手があったか。参考になるなぁ。

コメントを残す