Skip to content

Latest commit

 

History

History
182 lines (140 loc) · 15.8 KB

03.md

File metadata and controls

182 lines (140 loc) · 15.8 KB

Day 03

Part 1

Count the trees along the way to the toboggan airport.

I tried to be quick, so at first I wrote this messy code:

const input = `
  ..##.......
  #...#...#..
  .#....#..#.
  ..#.#...#.#
  .#...##..#.
  ..#.##.....
  .#.#.#....#
  .#........#
  #.##...#...
  #...##....#
  .#..#...#.#
`
  .trim()
  .split('\n')
  .map((row) => row.trim())

let x = 0
let result = 0

input.forEach((row) => {
  if (row[x % row.length] === '#') result++
  x += 3
})

console.log(result)

After verifying that the above code works, I refactored the mutable variables away:

const result = input.reduce(
  (acc, row, i) => (row[(3 * i) % row.length] === '#' ? acc + 1 : acc),
  0
)

console.log(result)

Try it out on flems.io

Part 2

Same as Part 1, but there are many slopes, and their tree counts should be multiplied together.

We just™ need to

  • slightly modify our reducer to correctly handle rows where y > 1
  • use another reducer for the multiplication.

Behold:

const slopes = [
  { x: 1, y: 1 },
  { x: 3, y: 1 },
  { x: 5, y: 1 },
  { x: 7, y: 1 },
  { x: 1, y: 2 },
]

function countTreesInSlope(slope) {
  return input.reduce((acc, row, i) => {
    if (i % slope.y !== 0) return acc

    return row[((slope.x * i) / slope.y) % row.length] === '#' ? acc + 1 : acc
  }, 0)
}

const result = slopes.reduce((acc, slope) => acc * countTreesInSlope(slope), 1)
console.log(result)

flems

That's a bit sloppy (pun intended), but hey, it works.

The first line of the first reducer (the if statement) is basically filtering some rows. So using filter() before the reducer makes sense and makes the code a bit cleaner:

function countTreesInSlope(slope) {
  return input
    .filter((row, i) => i % slope.y === 0)
    .reduce(
      (acc, row, i) =>
        row[(slope.x * i) % row.length] === '#' ? acc + 1 : acc,
      0
    )
}

const result = slopes.reduce((acc, slope) => acc * countTreesInSlope(slope), 1)
console.log(result)

flems

Finally, I replaced the reducer with another filter. The filter might not be as semantic as the reducer, but I think it makes the code cleaner:

function countTreesInSlope(slope) {
  return input
    .filter((row, i) => i % slope.y === 0)
    .filter((row, i) => row[(slope.x * i) % row.length] === '#').length
}

const result = slopes.reduce((acc, slope) => acc * countTreesInSlope(slope), 1)
console.log(result)

flems

What did I learn?

I didn't learn anything new from doing the puzzle, but later on this day I learned something distantly relevant about reduce(): specifying initialValue (the second argument) for a reducer is optional. From MDN's reduce() page about initialValue:

A value to use as the first argument to the first call of the callback. If no initialValue is supplied, the first element in the array will be used as the initial accumulator value and skipped as currentValue. Calling reduce() on an empty array without an initialValue will throw a TypeError.

I first saw this in Sam Selikoff's video Tailwind CSS Tips, Tricks & Best Practices. He wrote this piece of code (slightly adapted):

const aspectRatio = '16:9'
const paddingBottom = aspectRatio
  .split(':')
  .reduce((first, second) => second / first)

return <div style={{ paddingBottom: `${paddingBottom * 100}%` }} />

Fascinating!

I would have calculated paddingBottom probably like this:

const aspectRatio = '16:9'
const [first, second] = aspectRatio.split(':')
const paddingBottom = second / first

But the reducer version is interesting. Albeit still a bit confusing as I only just learned that specifying initialValue is optional. But interesting!

One thing to keep in mind is the last sentence from the MDN quote:

Calling reduce() on an empty array without an initialValue will throw a TypeError.