I love December, somehow it has more of everything - both good and bad. Advent of Code, in recent years has curved its own presence. I have certain complaints about it, namely I don't quite enjoy most of the puzzles and I feel like puzzle design itself is somewhat ill influenced by the speed aspect of the leaderboard. That said, it's now a massive hit which means there exists awesome learning opportunity in interaction with the community. Besides, my recreational programming has become non-existent in recent years, so I try to engage as much as I can (though the reasons it is non-existent in the first place don't allow me to see it through to the end). I am thinking of documenting my run this year (and the learnings therein), though not in more than an offhand capacity.
Anyway, I am primarily interested in learning. The subreddit is where the community is, and I have written about Pushshift API for interacting with reddit more efficiently before. I am saving all comments from a given thread like:
#!/bin/sh
link_id="$(echo $1 | rg -o 'comments/(.*?)/' -r '$1')"
alias sred="http https://api.pushshift.io/reddit/search/comment/"
sred link_id==$link_id size==1000 > ./reddit
And then you can do all kinds of things like:
jq '.data[] | select(.author == "askalski") | .body' ./reddit
Also, seems like people are taken with a pastebin service that encodes the entire source code into URL. Whole thing is entirely client side, and quite clever too. This will outlive all pastebin services, only slight issue with it is that you need to do this if you wish to extract data from the URL in command line:
#!/bin/sh
echo $1 | cut -d# -f2 | base64 -d | xz --format=lzma --decompress --stdout
Day 1 (Bash)
Part1 is about the common "map a function over all elements" pattern. In Raku (newly renamed from Perl6):
lines.map(*.Int div 3 - 2).sum.say;
In part 2, the elements themselves need to be expanded once more, so one more nested map or loop. For the special case when the relationship is self recursive (like here), Haskell has a cool function in prelude called iterate
that looks like:
iterate :: (a -> a) -> a -> [a]
iterate f a = a : iterate f (f a)
And then it's becomes as simple as
expand = concatMap $ tail . takeWhile (> 0) . iterate (\n -> n `div` 3 - 2)
main = print =<< sum . expand . map read . lines <$> getContents
Cool thing about lazy evaluation is that, now it's one big flat stream of values being summed on the fly. It tickles me that you get that pattern for free when you are abiding by Unix philosophy:
while read num; do
while true; do
num="$(($num / 3 - 2))";
if [ $num -lt 0 ]; then
break
fi
echo $num;
done
done | paste -sd+ | bc ;
Day 2 (Vala)
Day 2 and a VM already? At least it is straightforward, but it would surely pay to keep this code around for future. However I wanted to use a new language so as not to start repeating already (I will try to hold that off as long as possible). Now, you may not believe this, and I am also equally shocked at how similar it turned out, but the following is not actually C#, rather it's Vala! It's not really used outside of its intended purpose: which is to make writing GTK apps easier and interfacing with GLib and GObject. But talk about taking inspiration from C# (whose syntax highlighting I am using both in Emacs and here):
class Simulator {
private int[] code;
private int[] initial;
private int ip;
public Simulator(int[] initial) {
this.initial = initial;
}
private void run() {
while (true) {
switch(this.code[this.ip]) {
case 1:
this.code[this.code[this.ip+3]] =
this.code[this.code[this.ip+1]] + this.code[this.code[this.ip+2]];
break;
case 2:
this.code[this.code[this.ip+3]] =
this.code[this.code[this.ip+1]] * this.code[this.code[this.ip+2]];
break;
case 99:
return;
}
this.ip += 4;
}
}
public void reset(int noun, int verb) {
this.code = this.initial;
this.ip = 0;
this.code[1] = noun;
this.code[2] = verb;
}
public int search() {
this.run();
return this.code[0];
}
}
void main() {
int[] initial = {};
foreach (string num in stdin.read_line().split(",")) {
initial += int.parse(num);
}
var computer = new Simulator(initial);
for (int noun = 1; noun < 100; noun++) {
for (int verb = 1; verb < 100; verb++) {
computer.reset(noun, verb);
if (computer.search() == 19690720) {
stdout.printf("noun = %d, verb = %d\n", noun, verb);
}
}
}
}
I guess if you don't believe me, I will have to spin up a GTK GUI like this:
I believe the Solus (Budgie) and elementary OS (Pantheon) are some "modern" distros that still heavily use Vala for their apps. There is also Genie for when you need more syntactic sugar. I am a KDE/Qt person though, if I must choose a DE.
As for the problem, it's quite neat how the output is a simple linear function to noun and verb. I saw someone use Z3, I thing it would be quite impressive if Z3 recognizes the linear nature and find desired noun, verb for part2 in a non-exhaustive manner.
Day 3 (Perl)
I confess I confoundedly looked for central port at first because I thought it had to be given. But then I realised of course it doesn't matter from exactly where so long as both wires start from the same position. In my prototype, I gathered that all I need is a language with decent hash table, and by decent I mean I should be able to use basic but non-primitive data structure such as tuple or list as keys without needing to roll my own hashing function. But that's like every language, right?
So I chose Perl. Now, in the past more than a few AoC problems had felt like "a reminder once again that this is far easier in Perl". While that might prove to be the case later, what proved to be true now is my other impulse: Dread about having to use Perl. I speak of course as an amateur, but the whole $scalar
, @array
and %hash
distinction was very counterintuitive when nesting them. Not to mention, I had to serialize points into string anyway, ugh.
use strict;
use warnings;
use List::Util qw(min);
my %dir = (
U => [0, 1], D => [0, -1], L => [-1, 0], R => [1, 0]
);
my @wires;
for my $wire (0 .. 1) {
my ($x, $y, $z);
for (split ',', <STDIN>) {
$_ =~ m/(U|D|R|L)(\d+)/;
my ($dx, $dy) = @{$dir{$1}};
for (1 .. $2) {
$x += $dx; $y += $dy; $z += 1;
my $point = "$x%$y";
${$wires[$wire]}{$point} = $z unless
(exists ${$wires[$wire]}{$point}) ;
}
}
}
my (@part1, @part2);
foreach my $point (keys %{$wires[0]}) {
if (exists ${$wires[1]}{$point}) {
my ($x, $y) = split "%", $point;
push @part1, abs($x) + abs($y);
push @part2, ${$wires[0]}{$point} + ${$wires[1]}{$point};
}
}
print min(@part1), "\n";
print min(@part2), "\n";
Day 4 (PHP)
I was curious about solving this with regex. Since I prototyped with grep -P
, PHP was the least bothersome choice (due to PCRE support). Ensuring digits are sorted is done with this beauty /s:
/^0*?1*?2*?3*?4*?5*?6*?7*?8*?9*?$/
Part1 is then as simple as /(\d)\1/
but Part2 proved to be tricky. I just wanted to match anything of the form yxxz
. But the problem with the regex that matches it is the two edge case forms: ^xxz
at the beginning and yxx$
at the end. While the latter is easily solved, the former can't be (at least I don't see how) without using another pattern and then combining result. But as the saying goes (by me no less), a great many edge cases go away if you simply pad the input. So I left padded the number with 0 (I guess I should have used JS in order to take advantage of the awesome NPM ecosystem), and since my input range begins from 1xxxxx, it was fine.
<?php
$start = 158126;
$end = 624574;
$total = 0;
for ($num = $start; $num <= $end; $num++) {
if (preg_match("/^0*?1*?2*?3*?4*?5*?6*?7*?8*?9*?$/", $num)) {
$tmp = "0${num}";
if (preg_match("/(\d)(?!\1)(\d)\2(?!\2)(?:\d|$)/", $tmp)) {
$total += 1;
}
}
}
echo $total . PHP_EOL ;
?>
Day 5 (Nim)
Huh another Intcode. I have a prototype running in Nim, I think I will stick with one language for all of these problems rather than doing the same thing over and over again. Also, probably should post it once, because functionalities of earlier problems seem likely to be embedded in later ones.
Day 6 (Ruby)
Liked this, because it allowed to brush up on the classical search algorithms (DFS/BFS). Turned out that problem data creates a tree rather than a graph, so for part 2 you can just find the common ancestor instead of doing full blown BFS but whatever (I mean theoretically binary star system is a thing). I also don't use Ruby but could get this to work.
require 'set'
class Day6
attr_accessor :part1, :part2
def initialize
self.part1 = {}
self.part2 = {}
end
def create_graph
ARGF.each do |line|
earth, moon = line.chomp.split(")")
(self.part1[earth] ||= []) << moon
(self.part2[earth] ||= []) << moon
(self.part2[moon] ||= []) << earth
end
end
def part1_dfs(earth, depth)
return !self.part1[earth] ? 0 :
self.part1[earth].length + self.part1[earth]
.map {|moon| part1_dfs(moon, depth + 1) + depth}
.sum
end
def part2_bfs(source, destination)
current = source
depth = 0
visited = Set.new
visited << current
queue = []
while true
if current == destination
return depth - 2
end
self.part2[current].each do |neighbour|
if !visited.include?(neighbour)
queue.unshift [neighbour, depth + 1]
end
end
current, depth = queue.pop
visited << current
end
end
end
day6 = Day6.new
day6.create_graph
puts day6.part1_dfs("COM", 0)
puts day6.part2_bfs("YOU", "SAN")