「オフラインリアルタイムどう書く第5回(大貧民)」の問題を解いてみた


[横浜へなちょこプログラミング勉強会](https://yhpg.doorkeeper.jp/)にて過去に出題された[大貧民](http://nabetani.sakura.ne.jp/hena/ord5dahimi/)を解いてみた。
回答にかかった時間は40分ぐらい。

回答自体は30分程度で終えていたのだが、「出力については、可能な手の順序も、可能な手の中のカードの順序も問わない」ためにテストが通らなかったことに気付かず無駄に時間を食ってしまった。
結局テストのほうで期待値もソートしてしまうことにした。

```ruby
class Game
  def initialize input
    ground, deck = input.split ","
    @deck = Deck.new deck || ""
    @ground = Deck.new ground || ""
  end

  def choice
    list = @deck.larger(@ground)
    list.empty? ? "-" : list.map{|group|
      group.map{|card| card.name}.join
    }.join(",")
  end
end

class Deck
  def initialize input
    @deck = input.each_char.each_slice(2).map{|c| Card.new *c}
  end

  def size
    @deck.size
  end

  def larger other
    number = other.number
    return [] if number.nil?
    numbers = @deck.each_with_object([]){|c, r|
      r << c.number if c.number > number
    }.uniq.each_with_object([]){|num, r|
      group = @deck.select{|card| card.number == num or card.joker?}
      if group.size >= other.size
        group.combination(other.size){|g| r << g}
      end
    }.uniq
  end

  def number
    numbers = @deck.map{|card| (@deck.size > 1 and card.joker?) ? nil : card.number}.compact.uniq
    numbers.size === 1 ? numbers.first : nil
  end
end

class Card
  attr_reader :number
  NUMBER = {
    "3" => 1, "4" => 2, "5" => 3, "6" => 4,
    "7" => 5, "8" => 6, "9" => 7,
    "T" => 8, "J" => 9, "Q" => 10,
    "K" => 11, "A" => 12, "2" => 13
  }
  def initialize suit, number
    @suit = suit
    @number = ("#{suit}#{number}" === "Jo") ? 14 : NUMBER[number]
  end

  def name
    joker? ? "Jo" : "#{@suit}#{NUMBER.key @number}"
  end

  def joker?
    @number === 14
  end

  def <=> other
    if other.is_a? Numeric
      @number <=> other
    elsif other.is_a? Card
      @number <=> other.number
    else
      raise ::ArgumentError
    end
  end
end

test = <<_TEST
/*#1*/ test( "DJ,", "-" );
/*#2*/ test( "H7,HK", "HK" );
/*#3*/ test( "S3,D4D2", "D4,D2" );
/*#4*/ test( "S9,C8H4", "-" );
/*#5*/ test( "S6,S7STCK", "CK,ST,S7" );
/*#6*/ test( "H4,SAS8CKH6S4", "S8,CK,H6,SA" );
/*#7*/ test( "ST,D6S8JoC7HQHAC2CK", "Jo,C2,CK,HA,HQ" );
/*#8*/ test( "SA,HAD6S8S6D3C4H2C5D4CKHQS7D5", "H2" );
/*#9*/ test( "S2,D8C9D6HQS7H4C6DTS5S6C7HAD4SQ", "-" );
/*#10*/ test( "Jo,HAC8DJSJDTH2", "-" );
/*#11*/ test( "S4Jo,CQS6C9DQH9S2D6S3", "DQCQ,D6S6,H9C9" );
/*#12*/ test( "CTDT,S9C2D9D3JoC6DASJS4", "JoC2,SJJo,DAJo" );
/*#13*/ test( "H3D3,DQS2D6H9HAHTD7S6S7Jo", "JoHA,JoD6,JoH9,D6S6,D7S7,JoS6,HTJo,JoDQ,S2Jo,JoD7,JoS7" );
/*#14*/ test( "D5Jo,CQDAH8C6C9DQH7S2SJCKH5", "CQDQ" );
/*#15*/ test( "C7H7,S7CTH8D5HACQS8JoD6SJS5H4", "HAJo,JoSJ,H8S8,H8Jo,CQJo,CTJo,JoS8" );
/*#16*/ test( "SAHA,S7SKCTS3H9DJHJH7S5H2DKDQS4", "-" );
/*#17*/ test( "JoC8,H6D7C5S9CQH9STDTCAD9S5DAS2CT", "CTDT,H9D9,S9D9,DACA,CTST,H9S9,DTST" );
/*#18*/ test( "HTST,SJHJDJCJJoS3D2", "DJCJ,SJDJ,JoHJ,CJHJ,SJJo,HJSJ,DJJo,JoCJ,JoD2,SJCJ,DJHJ" );
/*#19*/ test( "C7D7,S8D8JoCTDTD4CJ", "D8S8,JoS8,CTJo,DTJo,JoCJ,CTDT,D8Jo" );
/*#20*/ test( "DJSJ,DTDKDQHQJoC2", "JoDK,HQDQ,DQJo,C2Jo,JoHQ" );
/*#21*/ test( "C3H3D3,CKH2DTD5H6S4CJS5C6H5S9CA", "S5H5D5" );
/*#22*/ test( "D8H8S8,CQHJCJJoHQ", "JoCQHQ,JoHJCJ" );
/*#23*/ test( "H6D6S6,H8S8D8C8JoD2H2", "D2H2Jo,D8JoS8,D8S8C8,C8D8H8,JoC8S8,H8JoC8,S8H8C8,JoS8H8,C8JoD8,D8H8S8,D8JoH8" );
/*#24*/ test( "JoD4H4,D3H3S3C3CADASAD2", "DACASA" );
/*#25*/ test( "DJHJSJ,SQDQJoHQCQC2CA", "SQJoCQ,DQCQJo,JoSQHQ,SQCQHQ,DQHQSQ,HQDQCQ,HQDQJo,SQDQCQ,CQJoHQ,SQJoDQ" );
/*#26*/ test( "H3D3Jo,D4SKH6CTS8SAS2CQH4HAC5DADKD9", "HASADA" );
/*#27*/ test( "C3JoH3D3,S2S3H7HQCACTC2CKC6S7H5C7", "-" );
/*#28*/ test( "H5C5S5D5,C7S6D6C3H7HAH6H4C6HQC9", "C6D6S6H6" );
/*#29*/ test( "H7S7C7D7,S5SAH5HAD5DAC5CA", "SADACAHA" );
/*#30*/ test( "D4H4S4C4,S6SAH6HAD6DAC6CAJo", "C6H6S6D6,SAJoDACA,S6H6C6Jo,SACAJoHA,HADASAJo,HADAJoCA,CADAHASA,D6C6JoH6,S6D6C6Jo,H6JoS6D6" );
/*#31*/ test( "DTCTSTHT,S3SQH3HQD3DQC3CQJo", "HQSQJoDQ,SQCQDQJo,DQCQHQJo,SQHQJoCQ,CQDQHQSQ" );
/*#32*/ test( "JoS8D8H8,S9DTH9CTD9STC9CAC2", "H9C9D9S9" );
_TEST

require 'minitest/autorun'

describe 'Game' do
  test.split("\n").each do |line|
    t, n, input, expect = line.match(/^\/\*#(\d+)\*\/\stest\(\s+"([^"]+)",\s+"([^"]+)"\s*\);$/).to_a
    game = Game.new input
    it input do
      e = expect.split(",").map{|c| c.each_char.each_slice(2).map{|n| n.join}.sort.join}.sort.join(",")
      r = game.choice.split(",").map{|c| c.each_char.each_slice(2).map{|n| n.join}.sort.join}.sort.join(",")
      assert_equal e, r
    end
  end
end
```