argius note

プログラミング関連

ポーカーの役判定

久々にどう書く?orgに挑戦。既に半月前のですが、何とか自分でもヒントとか無しで解けそうだったのでやってみました。こういうのをLispHaskellで、すらすら書けようになりたい。
本体はpoker_hand。それより下はテスト実行用。本体はごちゃっとしたのをすっきりさせたいところですが、時間切れです。

def poker_hand(card)
#  suits, numbers = card.split(//).partition do |c| c[/[SCHD]/] end
  suits = (0..4).collect do |i| card[i * 2, 1] end
  numbers = (0..4).collect do |i|
    " A23456789TJQK".index(card[i * 2 + 1, 1])
  end.sort # 事前ソート
#  p suits, numbers
  straight = numbers == (numbers[0]..numbers[0]+4).to_a
  if suits.uniq.size == 1 # flush
    return "Royal flush" if numbers == [1, 10, 11, 12, 13]
    return "Straight flush" if straight
    return "Flush"
  elsif straight
    return "Straight"
  elsif numbers.uniq.size == 2
    # 11112 12222 / 11122 11222
    if numbers[1] == numbers[3] # 2番目と4番目が一緒
      return "Four of a kind"
    else
      return "Full house"
    end
  elsif numbers.uniq.size == 3
    # 11223 11233 12233 / 11123 12223 12333
    if numbers.select do |n| n == numbers[2] end.size == 3
      # 3番目と同じ要素が3個
      return "Three of a kind"
    else
      return "Two pair"
    end
  elsif numbers.uniq.size == 4
    return "One pair"
  end
  "No pair"
end

DATA.readlines.each do |line|
  card, expect = line.chomp.split(/ /, 4)[2..3]
  result = poker_hand(card)
  f = if result == expect then "OK" else "NG" end
  puts "[#{f}] card:#{card}, expect:#{expect}, result:#{result} "
end

__END__
% ./poker SQSJSASKST Royal flush
% ./poker D9D7D6D5D8 Straight flush
% ./poker C2D2S2H3H2 Four of a kind
% ./poker C2D3S2H3H2 Full house
% ./poker S9S4S8STSJ Flush
% ./poker C4H7D5S6H3 Straight
% ./poker S6H6C5DQC6 Three of a kind
% ./poker S6HQC5DQC6 Two pair
% ./poker S6H4C5DQC6 One pair
% ./poker SJSQSKSAC2 No pair

追記:Haskellバージョン改良版を元に整理してみました。もしかしたら、正規表現を駆使すればもっと簡潔に書けるのかもしれませんが、文字列でなく数列として考えてみたかったので、配列による判定となっています。

def poker_hand(cards)
  indices = (0..9).partition do |i| i % 2 == 0 end
  suits = indices[0].collect do |i| cards[i].chr end
  ranks = indices[1].collect do |i| cards[i].chr end.sort
  numbers = ranks.collect do |rank| "A23456789TJQK".index(rank) + 1 end.sort

  flush = suits.uniq.size == 1
  straight = numbers == (numbers[0]..numbers[0]+4).to_a

  return "Royal flush"     if flush && ranks.join == "AJKQT"
  return "Straight flush"  if flush && straight
  return "Flush"           if flush
  return "Straight"        if straight

  groups = ranks.uniq.collect do |item|
    ranks.select do |rank| rank == item end.size
  end.sort.reverse

  return "Four of a kind"  if groups == [4, 1]
  return "Full house"      if groups == [3, 2]
  return "Three of a kind" if groups == [3, 1, 1]
  return "Two pair"        if groups == [2, 2, 1]
  return "One pair"        if groups == [2, 1, 1, 1]
  return "No pair"
end