Caleb Weeks

Posted on

# Advent of Code #7 (in Crystal)

Today's problem was primarily an exercise in sorting, with a little bit of math/logic for part 2. Instead of assigning a new order for each card, I kept the numbers the same and assigned new values to the face cards (and the number 10). Then I used `Array.group_by` and some patter matching to determine the order of the type of hand.

I don't know of any language that features composition of comparisons. Comparisons are usually represented as `-1/0/1` for less than, equal, or greater, but are sometimes represented as an enum or tagged union such as `LT/EQ/GT`. I would imagine such a feature to take two comparisons and return the result of the second if the first is equal. It is surprising how often I run into this situation. Maybe when I finally get around to writing my own language, I'll add this feature.

The last thing I wanted to discuss is code readability (or whatever word you want to use for the quality of code). Functional programming is often labeled as a paradigm that leans into math (perhaps too much). While I think that is a misrepresentation, it is often true that a functional style of code lacks descriptiveness. The OOP approach of representing everything as an object has the advantage of forcing you to use descriptive names.

It is certainly possible to write FP style code that uses more intermediate variables and even objects to convey intent and meaning more clearly. But coming up with those descriptive names takes careful consideration, and can slow down the 'real' code.

Sometime, I would like to discuss this more and show examples of improved readability of FP code. But today is not that day, so here is the mess of code I came up with:

``````input = File.read("input").strip

cards = input.split("\n").map do |line|
card, bid = line.split(' ')
bid = bid.to_i
card = card.gsub({T: ':', J: ';', Q: '<', K: '=', A: '>'})
{card, bid}
end

def type(a)
cases = {
[] of Int32 => 7,
[1] => 7,
[2] => 7,
[1, 1] => 6,
[3] => 7,
[1, 2] => 6,
[1, 1, 1] => 4,
[4] => 7,
[1, 3] => 6,
[2, 2] => 5,
[1, 1, 2] => 4,
[1, 1, 1, 1] => 2,
[5] => 7,
[1, 4] => 6,
[2, 3] => 5,
[1, 1, 3] => 4,
[1, 2, 2] => 3,
[1, 1, 1, 2] => 2,
[1, 1, 1, 1, 1] => 1
}

hand = a
.gsub('1', "")
.chars
.group_by { |x| x }
.values
.map(&.size)
.sort

cases[hand]? || 1
end

def stronger_hand(a, b)
compare_type = type(a) <=> type(b)
compare_type == 0 ? a <=> b : compare_type
end

part1 = cards
.sort { |(a, _), (b, _)| stronger_hand(a, b) }
.map_with_index(1) { |(_, bid), i| bid * i }
.sum

puts part1

part2 = cards
.map { |card, bid| {card.gsub(';', '1'), bid} }
.sort { |(a, _), (b, _)| stronger_hand(a, b) }
.map_with_index(1) { |(_, bid), i| bid * i }
.sum

puts part2
``````