1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
HAND_STRENGTH = [
  [5],             # five of a kind
  [1, 4],          # four of a kind
  [2, 3],          # full house
  [1, 1, 3],       # three of a kind
  [1, 2, 2],       # two pair
  [1, 1, 1, 2],    # one pair
  [1, 1, 1, 1, 1], # high card
].reverse.each.with_index.to_h { [_1, _2] }

class PartOne
  CARD_RANK = ((?2..?9).to_a + %w[ T J Q K A ]).each.with_index.to_h

  def strength(cards)
    tally = cards.tally
    strengths = [
      self.hand_type(tally),
      *cards.map { CARD_RANK.fetch(_1) },
    ]
    strengths.map { (?a.ord + _1).chr }.join
  end

  def hand_type(tally)
    HAND_STRENGTH.fetch(tally.values.sort)
  end
end

class PartTwo < PartOne
  CARD_RANK = ([?J] + (?2..?9).to_a + %w[ T Q K A ]).each.with_index.to_h

  def hand_type(tally)
    jokers = tally.delete(?J) || 0
    counts = tally.values.sort
    if counts.empty?
      counts = [5]
    else
      counts[-1] += jokers
    end
    HAND_STRENGTH.fetch(counts)
  end
end

hands = ARGF.read.scan(/(\w+)\s+(\d+)/).to_h { [_1.chars, _2.to_i ] }

part_one = PartOne.new
p hands
  .sort_by {|(hand,_)| part_one.strength(hand) }
  .map.with_index {|(_,bid),i| bid * (i+1) }
  .sum

part_two = PartTwo.new
p hands
  .sort_by {|(hand,_)| part_two.strength(hand) }
  .map.with_index {|(_,bid),i| bid * (i+1) }
  .sum