Advent of Code - Year 2015, Day 06
Problem statement: http://adventofcode.com/2015/day/6
Part A
In order to solve this problem, we have to keep track of the state of each light. The easiest way to do this is to keep a two dimensional array (bool[,]
or int[,]
) of lights in the current state. Then for each instruction in the list, we process the instruction by going through the lights specified, and performing the action described. Once broken down, the concept is not overly complicated, but it does require some real code.
Part B
The only real difference between Part A and Part B is that Part B requires an int[,]
and the instructions deal with modifying the integer values instead of flipping a bit.
Regex
We could write a basic parser for the instructions, and if you look at the file history for the .cs
version, you'll find that originally I did use a string parser for the instructions. However, regex makes extracting the command and values much easier.
@"((?<on>turn on)|(?<off>turn off)|(?<toggle>toggle)) (?<startX>\d+),(?<startY>\d+) through (?<endX>\d+),(?<endY>\d+)"
I'll start with the second half of the regex. If you pay attention, you'll notice that (?<startX>\d+)
looks exactly like the (?<w>\d+)
from day 3. There are four numbers which are important, in two coordinate pairs. The word through
is in the middle, so the second half looks for 123,123 through 124,124
exactly. The names startX
and similar are used to extract the number strings, which we can then convert from string to integer.
The first part is slightly more complicated. Any time a |
is used in the regex, it means that we want the parser to pick either the item on the left or the item on the right. It is a basic OR statement for regex. So here, (stripping out the syntax) we are saying that we want to match the strings turn on
, turn off
, OR toggle
. Since we have applied a group to each of these strings, we can test for the existence of the group in the code. You'll see this as m.Groups["on"].Success
, which says that the group (?<on>...)
group was matched. We can use this information to convert the first part of the string into information about which type of instruction was used.
C#
source
For each instruction, we need to keep track of a) which command we received, and b) the coordinate values to apply the command. Since this is common to both Part A and Part B, we can do this once and save it into a List<Action>
.
ProcessActions()
is also the same for both Part A and Part B: in both parts we need to for each instruction, figure out what to do based on the Command
, effect the instructions for each light in the specified rectangle, and finally calculate the sum (part A = how many lights are on; part B = the total value for each light).
Since we have abstracted this information, we can simply call ProcessActions()
with a getLightProcessor
, which will determine by Command
what action we want to perform on a light. For Part A, we specify that TurnOn
sets the light value to 1
, TurnOff
sets the light value to 0
, and Toggle
flips the value between 0
and 1
.
Similarly, for Part B, we specify by Command
to perform the action described in the problem statement.
PS: I like the new C# feature for the throw
-expression. This means I can explicitly specify for all three Command
s and throw an exception if the Command
is not one that I am expecting.
F#
source
Perhaps not surprising, the F# code is somewhat more concise than the C# code. Setting up the Command
and Action
are the same as the C#; and the basic structure of processActions
is the same as C#'s ProcessActions()
. The most interesting part of the F# is partA
and partB
.
As a side note, technically every function that takes more than one parameter does not actually receive every parameter. Instead, it takes a single parameter and returns a new function that takes one less function, where the first parameter is included as a closure, similar to lambdas.
For the C# aficionados in the crowd, int Add(int a, int b) { return a + b; }
actually looks like Func<int, int> Add(int a) { return (Func<int, int>)(b => a + b); }
.
Knowing this, partA
and partB
call processActions
and return a new function with the getLightProcessor
value specified, which means they can be called as functions as well. The official name for this concept is 'currying', and it allows for some interesting techniques like the one shown here.
The other interesting thing about partA
and partB
is the match
expression. This works similar to a switch statement: the value c
is matched with the enumerated values in Command
and the value specified is returned. This biggest advantage to the match
expression over a basic switch statement is that since I am matching on the enum Command
, F# will complain if not every value in Command
is provided. This can be useful for long-term code to know if I added a new Command
that I would be warned about it in each case it is match
ed.