Advent of Code 2021: Day 3

Search for a command to run...

Thank you so much for your series on the Advent of Code2021. Without you, I would have missed it!
In this series we solve programming challenges from the Advent of Code 2021 using JavaScript.
This blog post is fourth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 4. The stories never stop surprising me. This one, in particular, is about playing bingo with a giant squid! Consider me ...
This blog post is fourteenth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 14. This challenge was about optimizing an algorithm that generates exponentially larger and larger strings. I found ...

This blog post is thirteenth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 13. All solutions are in this GitHub repository. Solving Part One Given a list of x, y coordinates representing dots ...

This blog post is twelfth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described on Day 12. All solutions are in this GitHub repository. Solving Part One Given a set of connections between underground caves, ...

This blog post is eleventh in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 11. This day had many similarities to Day 9, and I solved the challenge using similar methods. All solutions are in thi...

This blog post is the tenth in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 10. All solutions are in this GitHub repository. Solving Part One Given a set of lines with opening and closing bracke...

On this page
This blog post is third in the Advent of Code 2021 series and shows a JavaScript-based solution to the problem described in Day 3.
All solutions are in this GitHub repository.
The input file consists of binary numbers on separate lines. We must find the so-called gamma rate and epsilon rate, multiply them and return the resulting number in decimal format. Gamma rate is a binary number where each bit represents the most common bit in its position among all given numbers. Similarly, the epsilon rate contains the least common bit for each position.
We can break this problem into smaller tasks:
I wrote about the readInput function in my Advent of Code: Day 1 blog post. If you haven't already, give it a read!
import { URL, fileURLToPath } from 'url'
import { readInput } from '../utils/readInput.js'
const inputPath = fileURLToPath(new URL('./input.txt', import.meta.url))
const data = await readInput(inputPath)
We can then get each line containing a binary number by splitting the input data using the new line character \n
const lines = data.split('\n')
One of the constraints of this problem is that each number is represented using the same number of bits. Thus, we can calculate the length of the first line to find how many bits each number has.
const numberOfBits = lines[0].length
Now for each position, we must find the most and least common bits
for (let i = 0; i < numberOfBits; i++) {
// find most common bit for position i
// find least common bit for position i
}
We must iterate through each line and investigate the bit at the given position. To get the bit on a given position of a line we can use line.charAt(index). We can then count the occurrences of 0 and 1 and compare them in the end to determine the most common bit.
const getMostCommonBit = (lines, index) => {
let occurenceOf0 = 0
let occurenceOf1 = 0
lines.forEach((line) => {
const bit = line.charAt(index)
if (bit === '0') {
occurenceOf0 += 1
} else {
occurenceOf1 += 1
}
})
return occurenceOf0 > occurenceOf1 ? '0' : '1'
}
Since occurenceOf0 > occurenceOf1 can be written occurenceOf0 - occurenceOf1 > 0 we don't actually need the exact count of occurrences, only the difference. Thus, we can refactor the code like so:
const getMostCommonBit = (lines, index) => {
let occurenceDifference = 0
lines.forEach((line) => {
const bit = line.charAt(index)
if (bit === '0') {
occurenceDifference += 1
} else {
occurenceDifference -= 1
}
})
return occurenceDifference > 0 ? '0' : '1'
}
After finding the most common bit, we can determine the least common bit as the opposite. For example, if the most common bit is 0 we know the least common is 1 and vice versa.
const mostCommonBit = getMostCommonBit(lines, i)
const leastCommonBit = mostCommonBit === '0' ? '1' : '0'
let gammaRate = ''
let epsilonRate = ''
for (let i = 0; i < numberOfBits; i++) {
const mostCommonBit = getMostCommonBit(lines, i)
gammaRate += mostCommonBit
epsilonRate += mostCommonBit === '0' ? '1' : '0'
}
After finding the gammaRate and epsilonRate we can parse them to decimal using the parseInt function.
parseInt(gammaRate, 2)
parseInt(epsilonRate, 2)
You can see the final solution for part one my GitHub repository
I suggest thoroughly reading and understanding the problem before reading further.
To solve the second part of the challenge, we need to repeatedly filter the given binary numbers based on a bit criteria until there a single number is left. We must find the so-called oxygen generator rating (oxygenRate) and the CO2 scrubber rating (co2Rate) using this filtering method.
There are a few steps to solve the challenge:
oxygenRate and co2RateThe bit criteria for oxygenRate is the most common bit in position X among filtered numbers, where X starts at 0 and increases each filtering round. Similarly, the bit criteria for co2Rate is the least common bit.
We can reuse the getMostCommonBit function created for part one, but with changes to accommodate the additional rule - if there is an equal number of bits, the criteria should default to 1 for oxygenRate and 0 for co2Rate.
const getOccurenceDifference = (lines, index) => {
let occurenceDifference = 0
lines.forEach((line) => {
const bit = line.split('')[index]
if (bit === '0') {
occurenceDifference += 1
} else {
occurenceDifference -= 1
}
})
return occurenceDifference
}
The getOccurenceDifference returns a positive number if 0 is most common, a negative number if 1 is most common or 0 if both bits are equally common.
Thus, the bit criteria for oxygenRate is defined as follows and represents the most common bit or if both bits are equally common defaults to 1.
getOccurenceDifference(oxygenRates, index) > 0 ? '0' : '1'
The bit criteria for co2Rate represents the least common bit or if both bits are equally common defaults to 0.
getOccurenceDifference(co2Rates, index) > 0 ? '1' : '0'
The first filtering round starts with all lines for both oxygenRates and co2Rates. We can copy the lines representing the binary numbers into each corresponding array.
let oxygenRates = [...lines]
let co2Rates = [...lines]
We repeatedly filter these numbers until there is a single item left. Each time we must increase the index of bit we compare with criteriaBit.
let index = 0
while (oxygenRates.length > 1) {
const bitCriteria = getOccurenceDifference(oxygenRates, index) > 0 ? '0' : '1'
oxygenRates = oxygenRates.filter((line) => line.charAt(index) === bitCriteria)
index++
}
Similarly, we filter the co2Rates.
index = 0
while (co2Rates.length > 1) {
const bitCriteria = getOccurenceDifference(co2Rates, index) > 0 ? '1' : '0'
co2Rates = co2Rates.filter((line) => line.charAt(index) === bitCriteria)
index++
}
Finally, we can parse the first and only element in oxygenRates and co2Rates in the same manner we did for part one.
parseInt(oxygenRates[0], 2)
parseInt(co2Rates[0], 2)
You can see the final solution for part two my GitHub repository
Day 3 was about thoroughly reading the problem, understanding it and breaking it down into smaller parts. We processed strings representing binary numbers and relied on JavaScript functions like parseInt(binaryString, 2), String.charAt(index), array.filter(condition) to solve the challenges.