「あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定」をやってみた、その2

「あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定」をやってみた の続き。まだやってんのか! ええ、やってます(笑)

前回の日記の後、ググって色々な人のページを見てみた。“あなたのスキルで飯は食えるか?”の回答コード - FLYING さんが無駄がなく簡潔で凄い。凄すぎます。

あまりに凄かったので、前回のコードを考え直した。上記ページで id:tondol さんがおっしゃっている通り、取り得る牌の組み合わせは次の7種類。

[XY] [XZ] (XYZ) [X] (XX) [XX] (XXX)

前回のコードは牌の中の任意の場所から任意の組み合わせを取り出していたけれど、基本的に先頭のみを考えればよいのですね。あと、頭があるかどうか、待ちがあるかどうかのフラグがあればよいと。うむー、勉強になります。

というわけで、できたコードがこれ。

chin_itsu2.rb:

#!ruby

def main
  $result = []
  p = [0, 0, 0, 0, 0, 0, 0, 0, 0]
  ARGV[0].split(//).map { |c| c.to_i - 1 }.map { |i| p[i] = p[i] + 1 }
  search(p, '', nil, nil)
  puts $result.sort.uniq.join("\n")
end

# search: search mentsu(m), head(h), wait(w) from pai(p).
#
def search(p, m, h, w)
  #puts "#{p.to_s} : #{m}, #{h}, #{w}" # -- for debug
  p.shift while p[0] == 0
  return if p.any? { |d| d < 0 }
  if p.length == 0
    $result << ((h != :wait ? h : '') + "#{m}#{w}")
    return
  end
  x = 10 - p.length
  search(dec_times(p, 1), m, :wait, "[#{x}]") if !h && !w             # [1]
  search(dec_times(p, 2), m, h, "[#{x}#{x}]") if !w                   # [11]
  search(dec_times(p, 2), m, "(#{x}#{x})", w) if !h                   # (11)
  search(dec_times(p, 3), m + "(#{x}#{x}#{x})", h, w)                 # (111)
  return if x >= 9
  search(dec_index(p, [0, 1]), m, h, "[#{x}#{x + 1}]") if !w          # [12]
  return if x >= 8
  search(dec_index(p, [0, 2]), m, h, "[#{x}#{x + 2}]") if !w          # [13]
  search(dec_index(p, [0, 1, 2]), m + "(#{x}#{x + 1}#{x + 2})", h, w) # (123)
end

# dec_times: decrement first number of array specified times.
# ex. dec_times([7, 8, 9], 4) => [3, 8, 9]
#
def dec_times(a, n)
  a = a.clone
  n.times { a[0] = a[0] - 1 }
  a
end

# dec_index: decrement numbers in an array by indexes.
# ex. dec_index([1, 2, 3], [0, 1]) => [0, 1, 3]
#
def dec_index(a, idxs)
  a = a.clone
  idxs.each { |i| a[i] = a[i] - 1 }
  a
end

main

やった、超短くなったよー!

ポイントは「1223344888999」のような並びを次のように各桁の個数で格納していること。

1223344888999
↓
       89
 234   89
1234   89
                • -
122200033(各桁の個数)

ある面子が取れるかどうかのチェックは、とりあえず各桁の個数から1減らし、マイナスになった桁があったらダメだったと判断している。これでチェックと削除する作業を兼ねることができた。 訂正:エラーが出ないので牌を削除する作業(呼び出し元が担当)と削除できたかをチェックする作業(呼び出された側が担当)を分離することができ、コードがシンプルになった。

よし、なんとなく満足(笑)