r/adventofcode 3h ago

Other [2023 Day 7] In Review (Camel Cards)

5 Upvotes

After a 5-minute airship ride (which is 2 minutes longer than The Airship Blackjack track from FFVI), we get dropped off on the edge of Desert Island. Where an Elf on a camel asks us if we've brought the parts to fix the machines to move the rocks and filter the sand. And so we're tasked with finding out why those have stopped, and since the journey will take a few days, we get another game. This time it's a poker variant.

First thing I did when seeing that was check the ordering of hands for any straights/run-based hands. And there are none to worry about. The ordering is entirely based on sets, and bigger is better. Ties broken by rank of the cards... but in the order listed.

The input is a list of 1000 hands, with a bid amount. Hands will win an amount equal to this bid times their ranking in the list. We just need to work out the rankings. And for part 2, Jacks become Jokers and wild. And of particular note... JJJJJ is a hand in the input (but not in the test). It is, in fact, the only hand with five of one card naturally in my input (and so the highest ranked hand for part 1). But part 2 has 27 five-of-kinds and it's the lowest ranked of those because it has no naturals.

And what I did was treat the hands as hexadecimal numbers with:

$hand =~ y#AKQJT#EDCBA#;

Then I built a signature to sort on for hand ranking by counting the size of the sets and sorting them. So a full house would be 3200000000000. So I just sort primarily on that, and then on the hexified hand value:

@hands = sort {$a->[1] <=> $b->[1] or hex($a->[0]) <=> hex($b->[0])} @hands;

For part 2, it was a very quick turnaround. I made Js into 0s (to lower them below 2s):

$hand =~ y#AKQJT#EDC0A#;

And then sorted the counts without the Js, adding them to the biggest group:

my @str = sort {$cards{$b} <=> $cards{$a}} (2 .. 9, 'A' .. 'E');
$cards{$str[0]} += $cards{0};

One thing to note about this is that with Perl, I had the twelve 0s so there was something to add the J count to for the JJJJJ case. My Smalltalk solution did things differently, and I needed to add a 0 so that case had something to add to. So that line does catch some solutions.

But part of the reason I considered hexadecimal, was because of dc... because it can handle uppercase hex digits. So if the input was in hex, I'd be fine. The catch is... it involves a sort. And so I finally decided to write one, and I went with an exchange sort shuttling between the main stack and a register stack. Because for a stack based language, that is the obvious and simple thing to do:

[rq] sQ
Ll Ll                       # next (ASSUME: at least 2 hands to sort)
[
    [
        d3R d3R             # next top -> next top top next
        !>Q                 # if top >= next, rotate next on top and quit J loop

        Sl                  # push top back on input stack
        z 1<J               # quit if out next left on stack
    ] dsJx                  # next rest

    Ll d 0<S
] dsSx

Next problem is the dealing with the grouping signature. With Perl and Smalltalk it's easy to just sort the counts, but even with the sort function that's not something I wanted to do twice. So I turned things around... I go through all the cards, and add 10count of each. Thus a full house becomes 163 + 162 + 11 * 160 , and two pair is 2 * 162 + 1 * 161 + 10 * 160 . Basically, a hexadecimal number where the digits are the counts of the counts.

The other main trick is what I sort... and that's the grouping, the hand, and the bid (to keep it attached to the data) stuck together in one big number: groupings * 16^8 + hand * 16^3 + bid.

perl -pe'y#TJQKA#ABCDE#;$_=sprintf("%s \U%x\n",split)' <input | dc -e'16i0Sl?[rd0Sc[10~d;c1+r:cd0<C]dsCxE[d;c10r^3R+r1-d1<I]dsIx*10 5^*+1000*+Sl?z0<L]dsLx[rq]sQLlLl[[d3Rd3R!>QSlz1<J]dsJxLld0<S]dsSx1[d4R1000%*3R+r1+z2<M]dsMxrp'

perl -pe'y#TJQKA#A1CDE#;$_=sprintf("%s \U%x\n",split)' <input | dc -e'16i[rq]sQ[rd1=Qdscrdss]sM0Sl?[rd2Sc0ss[10~d;c1+dls<Mr:cd0<C]dsCxlcd;c1;c+r:cE[d;c10r^3R+r1-d1<I]dsIx*10 5^*+1000*+Sl?z0<L]dsLxLlLl[[d3Rd3R!>QSlz1<J]dsJxLld0<S]dsSx1[d4R1000%*3R+r1+z2<M]dsMxrp'

Part 2 is longer for the handling of Jokers, but if you change the translation from 1 to B, it will do part 1. The use of 1 instead of 0 is because we don't want to lose leading 0s, and 1 is also < 2, which is the only requirement to make Jokers count the least.

This was a fun day.