view day7b.rb @ 18:ddb69833346c

Implement day 11 distance finding in space "shortest distance" is simplified by it being cardinal directions, so it's the same as taking the right-angle between them. The Part 1 space expansion was quite clean, but the Part 2 approach generalises it to something even nicer.
author IBBoard <dev@ibboard.co.uk>
date Mon, 11 Dec 2023 20:08:47 +0000
parents 9ec95ff0d33d
children
line wrap: on
line source

#! /usr/bin/env ruby

require 'set'

if ARGV.length != 1
        abort("Incorrect arguments - needs input file")
elsif not File.exist? (ARGV[0])
	abort("File #{ARGV[0]} did not exist")
end

file = ARGV[0]

Hand = Struct.new(:cards, :score, :bid)

$card_scoring = ["J"].concat(("2".."9").to_a).concat(["T", "J", "Q", "K", "A"])

JOKER_VALUE = $card_scoring.index("J")
FULL_HOUSE = Set.new([2,3])
TWO_PAIR = {2 => 2, 1 => 1}

def score_hand(cards)
	card_vals = cards.each_char.map {|c| $card_scoring.index(c)}
	card_groups = card_vals.group_by{|v| v}.sort {|a,b|
		# Push jokers to the start so that we can find the biggest group
		# to add them to
		if a[0] == JOKER_VALUE then -1
		elsif b[0] == JOKER_VALUE then 1
		# Then order by number of values
		elsif a[1].length < b[1].length then -1
		elsif a[1].length > b[1].length then 1
		# Then order by card score
		else a[0] <=> b[0]
		end
	}
	jokers = card_vals.count(JOKER_VALUE)
	joker_replacement = card_groups.last[0]
	card_counts = card_groups.map {|v|
		# If it's the joker replacement, add the number of jokers (which may be 0)
		if v[0] == joker_replacement and v[0] != JOKER_VALUE then v[1].length + jokers
		# If it's the jokers and we're not a hand of jokers then remove them - they're added elsewhere
		elsif v[0] == JOKER_VALUE and joker_replacement != JOKER_VALUE then 0
		# Else just take the count
		else v[1].length
		end
	}.select {|num| num != 0}
	card_count_counts = card_counts.group_by{|v| v}.map{|k,v| [k, v.length]}.to_h
	dejokered_card_vals = card_vals.map {|v| v == 0 ? card_vals.max : v}
	of_a_kind = card_counts.max
	rank = of_a_kind * 10 # 5, 4 or 3 of a kind or a pair
	if Set.new(card_counts) == FULL_HOUSE
		rank = 35
	elsif card_count_counts == TWO_PAIR
		rank = 25
	end
	card_vals.reduce("#{rank}") {|str, v| "#{str}.#{(v+1).to_s.rjust(2, '0')}"}
end

hands = File.open(file, "r").each_line(chomp: true).map {|line| cards, bid = line.split(" "); Hand.new(cards, score_hand(cards), bid.to_i)}

puts hands.sort {|a, b| a.score <=> b.score}.map.with_index(1) {|val, i| puts "#{val}"; i * val.bid}.sum
# Rest of algorithm here