Day 5 involves a typical Advent of Code scenario where Part 1 is easy, and a naive modification to Part 1 to get Part 2 is easy; however, the naive Part 2 solution will take far too long to run! :)
After solving Part 1 to get a star, I re-implemented the code to solve Part 2, and then Part 1 was simply making the Part 1 input conform to what Part 2 needed, and call Part 2.
The "trick" for Part 2 was to process the seed ranges as ranges, and not attempt to convert the seed ranges into lists of individual seeds.
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
For today's task, I found it helpful to make use of the parallel-combine combinator from Hanson & Sussman's "Software Design for Flexibility". This combinator applies f and g to the input, in parallel, and combines their output using h. Pictorially, it looks like this:
My primary programming language is Racket,
so I expect to code most of the solutions in it; however, the rest of
my language stack includes Python, Javascript and C++, so I'll code
some of the solutions using them also.
Today's puzzle has very similar parts! For both parts, our input is a single string, and the task is to determine how many characters we need to consume before finding a contiguous group of N unique characters. The only difference is the value of N.
Supply Stacks
We're given the following sample input:
[D]
[N] [C]
[Z] [M] [P]
1 2 3
move 1 from 2 to 1
move 3 from 1 to 3
move 2 from 2 to 1
move 1 from 1 to 2
And, fortunately for us, the first part has lines padded with spaces to be of equal length - this makes parsing just a little bit easier :) Even so, the built-in parsers we have for AoC are insufficient, so we'll just use a custom parser that reads the file into a string, splits it on "\n\n", and then maps the string-split function over both parts - the stack lines and the command lines:
Before we get started with today's puzzle, I have some cleanup of my own to do!
1. all-from-out
At the beginning of each day, the second line of the prelude above is:
(require "../advent.rkt")
This is Racket's way of importing a module. Rather than also having to import common modules I typically use, such as the threading module, e.g. (require "../advent.rkt" threading), I'd like to be able to just import my advent.rkt module and get access to threading as well. I learned a long time ago that the answer to the question, "Can Racket do \<X>", is almost always, "yes", and this is no exception; it's accomplished with the all-from-out macro. My advent.rkt module already had (require threading) for it's own purposes, so I just needed a directive to have it export all of the exports from threading as if they were defined in advent.rkt:
(provide (all-from-out threading))
2. A parsing fix
One of the parsers I got from Peter Norvig is the numbers parser, and it allows parsing input such as 1,2 | -3,4 into a list of numbers, '(1 2 -3 4); however, today's input was like 49-51,31-50, and the numbers parser would output '(49 -51 31 -50. The - chars were meant to be separators, not negative signs. The original numbers parser used the regex pattern -?[0-9.]+. Modifying the regex pattern to be ((?<![0-9])-)?[0-9.]+ instead, which uses a negative lookbehind pattern, correctly outputs '(49 51 31 50).
The negative lookbehind pattern (?<![0-9]) is used in front of the -, so that the - will only match if it's not preceded by a numeric digit. This is a very handy regex technique.
Ok, with the modified numbers parser in place, let's parse today's input!