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の枚数をぶち込んで、枚数を勘定してその数字はメンツになれるかどうかを、一つ一つの数字ごとに判断するという手があったか。参考になるなぁ。
関連