Day 3: Binary Diagnostic 👯🎛️
I had to catch a plane this morning and the puzzle unlocked while getting onto the plane. Luckily I was able to solve part1 in the few minutes from the moment I sat on the plane to the moment they asked us to turn off the devices, so I could get also the text for part2 and had plenty of time during flight to solve it.
Today is also the day in which I finally realize a dream I have been having since last edition of Advent Of Code: I want to be able to listen to a puzzle input and I want to do it using the genuinely bizarre and bizarrely ingenious library paramidi by one of my favorite Nim authors: oakes (who is also responsible for vim_cubed, which I think is the most starred Nim repository after Nim compiler).
In order to do that, I accidentally released paramidib (not a typo) library, which is just two templates to have better access to paramidi through nimib. You can now follow along reading my rather dull solution for today or head directly for Part 3: Whale Music 🐳🎶.
Enjoy Advent of Nim! 🎄👑
Part 1
The diagnostic report (your puzzle input) consists of a list of binary numbers which, when decoded properly, can tell you many useful things about the conditions of the submarine. The first parameter to check is the power consumption.
You need to use the binary numbers in the diagnostic report to generate two new binary numbers (called the gamma rate and the epsilon rate). The power consumption can then be found by multiplying the gamma rate by the epsilon rate.
Each bit in the gamma rate can be determined by finding the most common bit in the corresponding position of all numbers in the diagnostic report. For example, given the following diagnostic report:
let testInput = """
00100
11110
10110
10111
10101
01111
00111
11100
10000
11001
00010
01010"""
Considering only the first bit of each number, there are five 0 bits and seven 1 bits. Since the most common bit is 1, the first bit of the gamma rate is 1.
The most common second bit of the numbers in the diagnostic report is 0, so the second bit of the gamma rate is 0.
The most common value of the third, fourth, and fifth bits are 1, 1, and 0, respectively, and so the final three bits of the gamma rate are 110.
So, the gamma rate is the binary number 10110, or 22 in decimal.
The epsilon rate is calculated in a similar way; rather than use the most common bit, the least common bit from each position is used. So, the epsilon rate is 01001, or 9 in decimal. Multiplying the gamma rate (22) by the epsilon rate (9) produces the power consumption, 198.
Use the binary numbers in your diagnostic report to calculate the gamma rate and epsilon rate, then multiply them together. What is the power consumption of the submarine?
let
testNums = testInput.splitLines.toSeq
puzzleNums = "2021/input03.txt".lines.toSeq
func getGamma(nums: seq[string]): int =
var
gamma = ""
countOnes: int
for i in 0 .. nums[0].high:
countOnes = 0
for num in nums:
if num[i] == '1':
inc countOnes
if countOnes >= nums.len div 2:
gamma.add '1'
else:
gamma.add '0'
result = parseBinInt gamma
dump testNums.getGamma
dump puzzleNums.getGamma
testNums.getGamma = 22 puzzleNums.getGamma = 2502
That's the right answer! You are one gold star closer to saving your vacation.
func part1(nums: seq[string]): int =
let
gamma = getGamma(nums)
# 2^5(=32) - 22 - 1 -> 9
epsilon = 2^len(nums[0]) - gamma
return gamma*epsilon
dump part1 testNums
dump part1 puzzleNums
part1 testNums = 220 part1 puzzleNums = 3988188
func lifeSupport(nums: seq[string], oxy=true): int =
var
nums = nums # idiomatic
i = 0
zeros, ones: seq[int]
while nums.len > 1:
zeros = @[] # zeros, ones = @[] does not work
ones = @[]
for j, num in nums:
if num[i] == '1':
ones.add j
else:
zeros.add j
#dump ones
#dump zeros
if oxy:
if ones.len >= zeros.len:
nums = collect:
for j in ones:
nums[j]
else:
nums = collect:
for j in zeros:
nums[j]
else:
if zeros.len <= ones.len:
nums = collect:
for j in zeros:
nums[j]
else:
nums = collect:
for j in ones:
nums[j]
inc i
#debugEcho nums
assert nums.len == 1
return parseBinInt(nums[0])
func part2(nums: seq[string]): int =
lifeSupport(nums)*lifeSupport(nums, false)
dump part2 testNums
dump part2 puzzleNums
part2 testNums = 230 part2 puzzleNums = 2555739
That's the right answer! You are one gold star closer to saving your vacation.
Part 3: Whale Music 🐳🎶
on top of the odd creaking noises that the submarine has been making in its descent, you start to hear a more pleasant melody that vibrates all through the hull. By looking outside the small circular windows you realize that you are surrounded by singing whales!
Having already completed your star collection for the day, you decide to dedicate the rest of the day (and night) to learn more about this songs.
Luckily, the library of the submarnim has a small booklet by one mysterious Kaz Cohaes entitled paralipomena of phalenotragoùdia that promises through the lost art of midi to allow you to be able to communicate with the singing whales!
You are definitely in luck today, since it appears that the key ingredient to make such a communication possible is a diagnostic report like the one produced by your submarine! You also happen to have the latest version of terminal N.I.M.I.B. (Natural Inducer of Music for Interesting Balenas) which you will use to solve the exercises proposed by the booklet.
The first chapter of PoP (short for Paralipomena of phalenotragoùdia) starts with the basic assumption of whale singing. Communication happen through musical scales and the utmost expressiveness is reached through alternating moments of music and moments of silence. Your first exercise is to translate your test diagnostic report into music.
Each line of the report is the repetition of the same scale, with the zeros representing moments of silence, while when a 1 is present the note must be emitted.
We will use library paramidi to produce the music, but we import it as paramidib (not a typo). This is a convenience thin layer that exposes two templates to save a paramidi score to a wave file and then to expose such a wave file through an html audio control.
Since the test diagnostic report is made of strings of 5 digits we will use a pentatonic scale. The primary way to write a paramidi music score is through a bizarre data structure which is a rather freely constructed hierarchy of tuples. If you find an instrument in the tuple, it means that what follows will be played with that instrument, a tempo annotation sets the tempo, a ratio of integers sets the length of next note, then finally come the notes which have the usual syntax through letter of the alphabet (there are ways also to change octave and to write chromatic notes). A letter r denotes a moment of rest (silence).
For the first exercise we will encode manually the score.
import paramidib
saveMusic("2021/day03test.wav"):
(piano,
(tempo: 109), # allegretto
1/5,
# A C D E G
# 0 0 1 0 0
r,r,d,r,r,
# 1 1 1 1 0
a,c,d,e,r,
# 1 0 1 1 0
a,r,d,e,r,
# 1 0 1 1 1
a,r,d,e,g,
# 1 0 1 0 1
a,r,d,r,g,
# 0 1 1 1 1
r,c,d,e,g,
# 0 0 1 1 1
r,r,d,e,g,
# 1 1 1 0 0
a,c,d,r,r,
# 1 0 0 0 0
a,r,r,r,r,
# 1 1 0 0 1
a,c,r,r,g,
# 0 0 0 1 0
r,r,r,e,r,
# 0 1 0 1 0
r,c,r,e,r
)
Good job on producing the first piece of music!
The second chapter of PoP tells you that longer sequence of digits may represent arpeggios of scales going up and down
The third chapter tells you that whale are apparently fond a bop and they like the color blue.
Since the puzzle input is made of sequences of 12 digits, we will use a blues scale which is made of 6 notes and we will go up and down the scale with an arpeggio.
Here is how this scale sound:
# blues scale: A C D Eb E G
saveMusic("2021/blues_scale.wav"):
(altosax,
(tempo: 234), 2/12,
-a, c, d, e♭, e, g,
a, g, e, e♭, d, c,
-a, c, d, e♭, e, g,
a, g, e, e♭, d, c,
2/12, -a, 1/12, c, 2/12, d, 1/12, e♭, 2/12, e, 1/12, g,
2/12, a, 1/12, g, 2/12, e, 1/12, e♭, 2/12, d, 1/12, c,
2/12, -a, 1/12, c, 2/12, d, 1/12, e♭, 2/12, e, 1/12, g,
2/12, a, 1/12, g, 2/12, e, 1/12, e♭, 2/12, d, 1/12, c,
)
Note that:
- we pass from a straight ryhthm to triplet swing
- the instrument is a sax alto and the tempo is the same of Charlie Bird's Ornithology (prestissimo)
-a
is used for aa
one octave lowerdx
is actually substituted through a source code filter todx
which representsd#
Now let's try to see how this would look with the moments of silence on zeros. We take the forst two lines of puzzle input to test how it sounds:
echo puzzleNums[0 .. 1]
@["111111010011", "110011001100"]
saveMusic("2021/day03puzzle_head.wav"):
(altosax,
(tempo: 234),
# 1 1 1 1 1 1
2/12, -a, 1/12, c, 2/12, d, 1/12, e♭, 2/12, e, 1/12, g,
# 0 1 0 0 1 1
2/12, r, 1/12, g, 2/12, r, 1/12, r , 2/12, d, 1/12, c,
# 1 1 0 0 1 1
2/12, -a, 1/12, c, 2/12, r, 1/12, r , 2/12, e, 1/12, g,
# 0 0 1 1 0 0
2/12, r, 1/12, r, 2/12, e, 1/12, e♭, 2/12, r, 1/12, r,
)
A tuple is an immutable data structure and since Nim is strongly typed it
is not the most flexible tool to write a score at runtime.
Luckily paramidi offers another way to write scores using json
variant objects.
import json
let puzzleHead =
%*["alto-sax", {"tempo": 234},
2/12, "a-", 1/12, "c", 2/12, "d", 1/12, "d#", 2/12, "e", 1/12, "g",
2/12, "r", 1/12, "g", 2/12, "r", 1/12, "r" , 2/12, "d", 1/12, "c",
2/12, "a-", 1/12, "c", 2/12, "r", 1/12, "r" , 2/12, "e", 1/12, "g",
2/12, "r", 1/12, "r", 2/12, "e", 1/12, "d#", 2/12, "r", 1/12, "r",
]
saveMusic("2021/day03puzzle_head_alt.wav", puzzle_head)
Note that:
- instead of
altosax
we need to usealto-sax
- instead of
-a
we usea-
- instead of
dx
we used#
In fact all this alternative syntax is the most natural one since it derives
from the C library that paramidi used to produce the sound. See the list of available constants here.
Now we are ready to create the score starting from a list of inputs in an automatic way:
func getScore(nums: seq[string], denominator=12): JsonNode =
const arpeggio = @["a-", "c", "d", "d#", "e", "g", "a", "g", "e", "d#", "d", "c"]
result = %*["alto-sax", {"tempo": 234}]
var
beat = true
i = 0
for num in nums:
for digit in num:
if beat:
result.add %(2/denominator)
else:
result.add %(1/denominator)
if digit == '1':
result.add %(arpeggio[i mod 12])
else:
result.add %"r"
inc i
beat = not beat
saveMusic("2021/day03puzzle_head_longer.wav", getScore(puzzleNums[0 ..< 10]))
saveMusic("2021/day03puzzle_head_faster.wav", getScore(puzzleNums[0 ..< 20], denominator=24))
We can also decide that we want to hear stuff at double the velocity.
Finally, I cannot put the full song since it is too big for github, but here is a 10 minute excerpt (full song is around 25 minutes):
saveMusic("2021/day03puzzle.wav", getScore(puzzleNums[0 .. 400]))
saveMusic("2021/day03puzzle_faster.wav", getScore(puzzleNums[0 .. 400], denominator=24))
you spend most of the night talking with whales through music, it is one of the most magical nights of your advent of code adventures.
Gotta be honest I did not expect the final music to be so good! I used it as soundtrack in loop while I was finishing writing the blogpost.
What a magical night with whale music and what a great Adventure Advent of Code is!