Advent of Code 2016 – Day 4 (Perl)

Previous:

Advent of Code 216 – Day 3

Advent of Code – Day 2

Advent Of Code 2016 – Day 1

Day 4

Start: 12/5/2016

Finish 12/6/2016

Language: Perl

SPOILER ALERT: If you have any inkling, any whatsoever, to work on the Advent of Code…. DO NOT READ THIS BLOG POST.  DO NOT LOOK AT MY GITHUB PROJECT.  It is no fun if you’re not solving it yourself, and you’ll feel so much better about yourself if you can figure it out without looking up an answer.  This blog post is not to give away the answer, but instead, is there for people to learn from.

As always, the following code is in my GitHub: https://github.com/pviafore/AdventOfCode2016

The Challenge

This day, the goal was to try and figure out which rooms out of a list were valid by matching up a checksum with their most frequent characters.  I really liked this idea.  What I didn’t like was the fact that I chose Perl for this.  I had never written Perl before in my life.  And I never want to ever again either.

Don’t get me wrong, I could see some wizard being able to write concise code that is really powerful in Perl.  I definitely saw the power.  But it wasn’t for me.  I’m not going to make the time investment in Perl when more readable options like Python exist (even if they aren’t as concise.)  Perl is like Makefiles.  I don’t want to maintain it.  One of my favorite quotes I’ve ever heard (don’t know who to attribute it to, but not me) is that “Perl is the only language that looks the same before an RSA encryption as it does afterwards.”

I gave Perl 6 a whirl, and was greeted with a problem right away.  I couldn’t find a way to install the Rakudo compiler.  It kept erroring out when I built it.  So, I decided to run it in docker (thank god for that, +1 for the Rakudo people for thinking of that).

I found the Perl docs to be almost as incomprehensible as most of the code, and it was very challenging to try and complete this challenge in a few hours.  I was also so used to Perl-compatible Regular Expressions in other languages, that I was not prepared for how Perl’s regular expressions actually manifested.

I’m sure the code isn’t Perl idiomatic, but you know what, it’s working.  I’m not proud of it, and I would never want to write this code for employment, but let’s take a look.


sub get-room-info (Str $room){
    if $room ~~ m/([|\-]*)(\d+)\[(*)\]/ {
        return (~$0, ~$1, ~$2)
    }
    die "INVALID ROOM ", $room;
}

sub get-checksum (Str $room) {
    my %mapping = ();
    my @chars = split "", $room;
    for @chars -> $char {
        if $char === "-" || $char === "" {

        }
        elsif %mapping{$char}:exists {
            %mapping{$char} =  %mapping{$char} + 1;
        }
        else {
            %mapping{$char} = 1
        }
    }
    my @sorted =  %mapping.keys.reverse.sort.sort: {%mapping{$^b}  %mapping{$^a} };
    return join "", @sorted[0..4];
}

sub is-valid-room((Str $room, Str $id, Str $chksum)) {
    return get-checksum($room) === $chksum;
}

my $data = slurp "/data/day4.txt";
my @rooms = split("\n", $data);
my @room-infos = @rooms.map({get-room-info $_ }).grep({is-valid-room $_});
my @sum = @room-infos.map({$_[1]}).sum;
say @sum;

So I’m going to start from the end and work my way up.

First the part, setting up the data flow.  Not too bad.


my $data = slurp "/data/day4.txt";
my @rooms = split("\n", $data);
my @room-infos = @rooms.map({get-room-info $_ }).grep({is-valid-room $_});
my @sum = @room-infos.map({$_[1]}).sum;
say @sum;

The interesting line is the map of get-room-info and the grep of is-valid-room.  (Man i spent a long time looking for a filter function.  I saw a map, but where was my filter.  Oh wait, it’s called grep.  Sigh)  Let’s take a look at get-room-info.


sub get-room-info (Str $room){
    if $room ~~ m/([|\-]*)(\d+)\[(*)\]/ {
        return (~$0, ~$1, ~$2)
    }
    die "INVALID ROOM ", $room;
}

Cue eye-bleach.  Regular expressions are a write-only language, god help you if you can read them fluently.  The room id is something like this : aaaaa-bbb-z-y-x-123[abxyz] where first comes the room name, then the sector id, then the checksum in brackets.  So naturally, my regular expression looks like this:


m/([|\-]*)(\d+)\[(*)\]/

I probably should have done named regular expressions for this, but I just couldn’t get them to work and it was late at night and I was growing weary of Perl.  (I had a headache the whole next day at work, I blame Perl).

So now that I deconstructed this string into a 3-tuple containing the name, id and chksum, I could filter out the invalid ones.

WARNING, this checksum code is ugly.  It took me forever to get Perl Hashes working correctly, and I still don’t know what I was doing wrong.


sub get-checksum (Str $room) {
    my %mapping = ();
    my @chars = split "", $room;
    for @chars -> $char {
        if $char === "-" || $char === "" {

        }
        elsif %mapping{$char}:exists {
            %mapping{$char} =  %mapping{$char} + 1;
        }
        else {
            %mapping{$char} = 1
        }
    }
    my @sorted =  %mapping.keys.sort.sort: {%mapping{$^b}  %mapping{$^a} };
    return join "", @sorted[0..4];
}

sub is-valid-room((Str $room, Str $id, Str $chksum)) {
    return get-checksum($room) === $chksum;
}

So that gave me the answer, and I went to bed.
One short-cut I want to highlight: Perl 6 sorting was stable, so I could sort on keys first, and then sort based on the highest mapping. This gave me a way to keep alphabetical order of keys if they had the same frequency.

Part 2

So I held off on part 2 until the next day.  This was to decode the room by doing a character shift a number of times equal to the sector-id.  Then I had to find the North Pole Storage Room in that list.

After sleeping on it, my impressions of Perl did not improve, but I had a much better idea what to do.   I modified the main control flow like it follows.


my $data = slurp "/data/day4.txt";
my @rooms = split("\n", $data);
my @room-infos = @rooms.map({get-room-info $_ })
                       .grep({is-valid-room $_})
                       .map({decrypt $_})
                       .grep({$_ ~~ m/"north"/});
say @room-infos;

Okay, so there is a new decrypt function.  I just map over the valid rooms and decrypt them, and then look for a string named “north” in the collection.

Here’s the decrypt operation:



sub shift($char, $id) {
    if ($char === "-" || $char === "") 
    {
        return $char;
    }
    my $int-value = ord($char);
    my $mod26 = ($int-value - 97 + $id) %26;

    return chr($mod26 + 97);
}

sub decrypt((Str $room, Str $id, Str $chksum)) {
    my @chars = split "", $room;
    my @decoded-room = @chars.map({ shift $_, $id});
    my $final-room = join "", @decoded-room;
    return ($final-room, $id, $chksum)
}

Its straightforward string manipulation, with the tricky part being a map of the shift operator.  The shift operator converts the char to ASCII, subtracts 97 (‘a’), adds the ID and mod 26, and then adds back 97 and converts it to a char.  I tried to chain functions a bit better, but Perl kept throwing errors at me, and I was so ready to move on at this point.

Interesting enough, the first time I had this, I saw n o r t h instead of north.  That’s because join “”, $var is different than join “”,@var even if the variable was calculated the same way.  (It may have been an earlier calculation that was treated different, but still, there is a difference in behavior based on the name of the variable.)

Wrap-up

I don’t ever want to touch Perl again.  The challenge was a fun one, but marred by my struggles in Perl.  I got the answers right on the first try each time, but between the compiler setup, the cryptic error messages, the obtuse language, and ill-explained docs, I couldn’t cut it.

Again, don’t get me wrong.  I’m sure there are greybeards out there who are awesome at Perl, and relish in the power it gives people.  I just did not find myself writing good readable code with how I was doing it.  The fault is on me there.

My overall grade I’m giving myself for this is a C.  While I did solve the problem, my code was not well structured, and I didn’t dive enough into the “why” of Perl to understand why things were the things they are.

Next up, Day 5 with Java!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s