大貧民（どう書く）

http://nabetani.sakura.ne.jp/hena/ord5dahimi/

トランプゲーム「大貧民」（「大富豪」ともいいます）の場の札と手札が与えられた場合、次に出せるすべての手の組み合わせを出力する問題です。

ジョーカーがワイルドカードになっているので、それをどう処理するかが考えどころになります。ここではジョーカーが手札に含まれている場合の処理を別にしました。

```ruby:Ruby
class Card
  Rank = %W(3 4 5 6 7 8 9 T J Q K A 2)
  def initialize(str)
    @value = str
    @rank = (str == "Jo") ? 13 : Rank.index(str[1])
  end
  attr_reader :value, :rank
  
  alias to_s value
end

module Daihinmin
  module_function
  
  def play(input)
    table, hand = input.split(",")
    return "-" unless hand 
    hand = hand.scan(/../).map { |c| Card.new(c) }
    joker = hand.find {|c| c.value == "Jo"}    
    table = table.scan(/../).map { |c| Card.new(c) }
    t_rank = table.map(&:rank).min
    t_num = table.size
    
    cs = hand.group_by(&:rank).select { |k, v| k > t_rank }.values
    result = cs.select { |ary| ary.size >= t_num }
               .flat_map { |ary| ary.combination(t_num).to_a }
    if joker && t_num >= 2
      result +=
        cs.select { |ary| ary.size >= t_num - 1 && ary[0] != joker }
          .flat_map {|ary|
            ary.combination(t_num - 1).map { |cards| cards + [joker] }
          }
    end
    
    result.empty? ? "-" : result.map(&:join).join(",")
  end
end


if __FILE__ == $0
  [
    "DJ,",
    "H7,HK",
    "S3,D4D2",
    "S9,C8H4",
    "S6,S7STCK",
    "H4,SAS8CKH6S4",
    "ST,D6S8JoC7HQHAC2CK",
    "SA,HAD6S8S6D3C4H2C5D4CKHQS7D5",
    "S2,D8C9D6HQS7H4C6DTS5S6C7HAD4SQ",
    "Jo,HAC8DJSJDTH2",
    "S4Jo,CQS6C9DQH9S2D6S3",
    "CTDT,S9C2D9D3JoC6DASJS4",
    "H3D3,DQS2D6H9HAHTD7S6S7Jo",
    "D5Jo,CQDAH8C6C9DQH7S2SJCKH5",
    "C7H7,S7CTH8D5HACQS8JoD6SJS5H4",
    "SAHA,S7SKCTS3H9DJHJH7S5H2DKDQS4",
    "JoC8,H6D7C5S9CQH9STDTCAD9S5DAS2CT",
    "HTST,SJHJDJCJJoS3D2",
    "C7D7,S8D8JoCTDTD4CJ",
    "DJSJ,DTDKDQHQJoC2",
    "C3H3D3,CKH2DTD5H6S4CJS5C6H5S9CA",
    "D8H8S8,CQHJCJJoHQ",
    "H6D6S6,H8S8D8C8JoD2H2",
    "JoD4H4,D3H3S3C3CADASAD2",
    "DJHJSJ,SQDQJoHQCQC2CA",
    "H3D3Jo,D4SKH6CTS8SAS2CQH4HAC5DADKD9",
    "C3JoH3D3,S2S3H7HQCACTC2CKC6S7H5C7",
    "H5C5S5D5,C7S6D6C3H7HAH6H4C6HQC9",
    "H7S7C7D7,S5SAH5HAD5DAC5CA",
    "D4H4S4C4,S6SAH6HAD6DAC6CAJo",
    "DTCTSTHT,S3SQH3HQD3DQC3CQJo",
    "JoS8D8H8,S9DTH9CTD9STC9CAC2"
  ].each do |input|
    puts Daihinmin.play(input)
  end
end
```

カードをどう表現するか迷って、ここではクラス化しました（`Card`クラス）。カードのランクを数値化し、弱い順に 0 からの Integer を与えてあります（`Card#rank`）。単独でいちばん強いジョーカーのランクは 13 です。String で表されるカードの表現は`Card#value`で取得できます。

処理としては、まず場の札を`table`、手札を`hand`とします。ジョーカーが存在すれば`joker`に入ります（存在しなければ`nil`）。
手札をランクによってグループ化し、場の札のランク（`t_rank`）よりランクの高いものだけを残したのが`cs`です。

`cs`から、場の札の枚数（`t_num`）以上のものを残し、あとは場の札の枚数だけの組み合わせを取って`result`に格納します。
手札にジョーカーがあり、かつ場の札の枚数（`t_num`）が2枚以上の場合は、ジョーカーをワイルドカードとしながらさらに同様のことをします。

結果を String で表現する（Array#join を使っています）ため、`Card#to_s`を`Card#value`のエイリアスとして設定しています。

なお、出力は順序の不定性があるので、テストをサボっています（笑）。

#追記
テストも書いてみました。

```ruby

if __FILE__ == $0
  def same?(input, expect)
    inputs = input.split(",")
    expects = expect.split(",")
    return false unless inputs.size == expects.size
    equal = ->(a, b) {
      a == b || a.scan(/../).sort == b.scan(/../).sort
    }
    is_found = ->(ans) {
      s = expects.find { |e| equal.(e, ans) }
      return false unless s
      expects.delete(s)
      true
    }
    inputs.all?(&is_found)
  end
  
  [
    ["DJ,", "-"],
    ["H7,HK", "HK"],
    ["S3,D4D2", "D4,D2"],
    ["S9,C8H4", "-"],
    ["S6,S7STCK", "CK,ST,S7"],
    ["H4,SAS8CKH6S4", "S8,CK,H6,SA"],
    ["ST,D6S8JoC7HQHAC2CK", "Jo,C2,CK,HA,HQ"],
    ["SA,HAD6S8S6D3C4H2C5D4CKHQS7D5", "H2"],
    ["S2,D8C9D6HQS7H4C6DTS5S6C7HAD4SQ", "-"],
    ["Jo,HAC8DJSJDTH2", "-"],
    ["S4Jo,CQS6C9DQH9S2D6S3", "DQCQ,D6S6,H9C9"],
    ["CTDT,S9C2D9D3JoC6DASJS4", "JoC2,SJJo,DAJo"],
    ["H3D3,DQS2D6H9HAHTD7S6S7Jo", "JoHA,JoD6,JoH9,D6S6,D7S7,JoS6,HTJo,JoDQ,S2Jo,JoD7,JoS7"],
    ["D5Jo,CQDAH8C6C9DQH7S2SJCKH5", "CQDQ"],
    ["C7H7,S7CTH8D5HACQS8JoD6SJS5H4", "HAJo,JoSJ,H8S8,H8Jo,CQJo,CTJo,JoS8"],
    ["SAHA,S7SKCTS3H9DJHJH7S5H2DKDQS4", "-"],
    ["JoC8,H6D7C5S9CQH9STDTCAD9S5DAS2CT", "CTDT,H9D9,S9D9,DACA,CTST,H9S9,DTST"],
    ["HTST,SJHJDJCJJoS3D2", "DJCJ,SJDJ,JoHJ,CJHJ,SJJo,HJSJ,DJJo,JoCJ,JoD2,SJCJ,DJHJ"],
    ["C7D7,S8D8JoCTDTD4CJ", "D8S8,JoS8,CTJo,DTJo,JoCJ,CTDT,D8Jo"],
    ["DJSJ,DTDKDQHQJoC2", "JoDK,HQDQ,DQJo,C2Jo,JoHQ"],
    ["C3H3D3,CKH2DTD5H6S4CJS5C6H5S9CA", "S5H5D5"],
    ["D8H8S8,CQHJCJJoHQ", "JoCQHQ,JoHJCJ"],
    ["H6D6S6,H8S8D8C8JoD2H2", "D2H2Jo,D8JoS8,D8S8C8,C8D8H8,JoC8S8,H8JoC8,S8H8C8,JoS8H8,C8JoD8,D8H8S8,D8JoH8"],
    ["JoD4H4,D3H3S3C3CADASAD2", "DACASA"],
    ["DJHJSJ,SQDQJoHQCQC2CA", "SQJoCQ,DQCQJo,JoSQHQ,SQCQHQ,DQHQSQ,HQDQCQ,HQDQJo,SQDQCQ,CQJoHQ,SQJoDQ"],
    ["H3D3Jo,D4SKH6CTS8SAS2CQH4HAC5DADKD9", "HASADA"],
    ["C3JoH3D3,S2S3H7HQCACTC2CKC6S7H5C7", "-"],
    ["H5C5S5D5,C7S6D6C3H7HAH6H4C6HQC9", "C6D6S6H6"],
    ["H7S7C7D7,S5SAH5HAD5DAC5CA", "SADACAHA"],
    ["D4H4S4C4,S6SAH6HAD6DAC6CAJo", "C6H6S6D6,SAJoDACA,S6H6C6Jo,SACAJoHA,HADASAJo,HADAJoCA,CADAHASA,D6C6JoH6,S6D6C6Jo,H6JoS6D6"],
    ["DTCTSTHT,S3SQH3HQD3DQC3CQJo", "HQSQJoDQ,SQCQDQJo,DQCQHQJo,SQHQJoCQ,CQDQHQSQ"],
    ["JoS8D8H8,S9DTH9CTD9STC9CAC2", "H9C9D9S9"],
  ].each do |input, expect|
    p same? Daihinmin.play(input), expect
  end
end
```