DEV Community

Cover image for Two-Factor Authentication
Robert Mion
Robert Mion

Posted on

Two-Factor Authentication

Advent of Code 2016 Day 8

Try the simulator using your puzzle input!

Message being revealed

Part 1

  1. A look back at message-unscrambling puzzles
  2. Marquee tool, circular operations, and nested lists
  3. Extracting each instruction's important bits
  4. Building the skeleton of my reduce()
  5. Handling rectangle instructions
  6. Handling row rotation instructions
  7. Handling column rotation instructions
  8. Revealing my message - and counting 'on' lights

A look back at message-unscrambling puzzles

Hmm. I thought there were more.

Anyway:

  • I had fun attempting each of those
  • I solved both parts of each one
  • I'm excited to attempt and solve this one!

Marquee tool, circular operations, and nested lists

This puzzle combines some familiar and novel concepts:

  1. String splitting or regular expressions to determine the instruction type, dimensions or row/column numbers and rotation amounts
  2. A marquee tool is represented by the continual selection of a rectangular subset of the grid from the top-left corner
  3. Circular operations are enacted by the rotation of values left-to-right wrapping and top-to-bottom wrapping
  4. Nested lists are the data structure I've always used to represent a grid of items

1,2 and 4 are simple enough by now.

3 seems simple - at least for row rotations.

But for column rotations, it may require some more complex programming.

Extracting each instruction's important bits

Example instructions include:

rect 14x1
rotate column x=12 by 1
rotate row y=0 by 40
Enter fullscreen mode Exit fullscreen mode

If using string splitting, I can depend on:

  • Items of length two to be rectangular selection instructions
  • For all other instructions, I can discern column/row from the second phrase

Building the skeleton of my reduce()

I'll process each line using reduce().

That way, I can package the mutating grid and the instructions together.

The starting state of the accumulating grid should be a nested array whose length is 6 and whose nested arrays' lengths are 50...all filled with .s.

My control flow therefore looks like:

input.reduce((grid, instruction) => {
  let parts = instruction.split(' ')
  if (parts.length == 2) {
    // Rectangle-selection rule
  } else if (parts[1] == 'column') {
    // Column rotation rule
  } else {
    // Row rotation rule
  }
}, new Array(6).fill(null).map(
     el => new Array(50).fill(null).map(el => ' ')
   )
)
Enter fullscreen mode Exit fullscreen mode

Handling rectangle instructions

My algorithm as pseudocode:

After splitting the instruction string at each space character into an array
  If the there are only two items in the array
    Split the second item at the x
      Coerce each string into a number
        Store as width and height
    Update the appropriate cells in the grid
      Starting from the top-left corner
        Setting each cell's value as #
Enter fullscreen mode Exit fullscreen mode

My algorithm as JavaScript:

let parts = instruction.split(' ')
if (parts.length == 2) {
  let [width, height] = parts[1].split('x').map(Number)
  for (let row = 0; row < height; row++) {
    for (let col = 0; col < width; col++) {
      a[row][col] = "#"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Handling row rotation instructions

My algorithm as pseudocode:

After splitting the instruction string at each space character into an array
  If no other conditions are true
    Use a regular expression to match all integers
      Store each integer as row and amount
    For i from 0 to amount
      Remove the last string in the nested array who's index matches row
        Add it to the beginning of that array
Enter fullscreen mode Exit fullscreen mode

My algorithm in JavaScript:

else {
  let [row, amount] = [...c.matchAll(/\d+/g)].map(el => +el[0])
  for (let i = 0; i < amount; i++) {
    a[row].unshift(a[row].pop())
  }
}
Enter fullscreen mode Exit fullscreen mode

Handling column rotation instructions

My algorithm as pseudocode:

After splitting the instruction string at each space character into an array
  If the second string is column
    Use a regular expression to match all integers
      Store each integer as column and amount
    For each nested array
      Extract the value from the appropriate column
        Store an array called band containing all strings from the column in order from top to bottom
    For i from 0 to amount
      Remove the last string in the array
        Add it to the beginning of the array
    For each nested array
      Update the value in the appropriate column with the corresponding string in the same index as the current nested array
Enter fullscreen mode Exit fullscreen mode

My algorithm in JavaScript:

else if (parts[1] == 'column') {
  let [column, amount] = [...c.matchAll(/\d+/g)]
                           .map(el => +el[0])
  let band = a.map(row => row[column])
  for (let i = 0; i < amount; i++) {
    band.unshift(band.pop())
  }
  for (let row = 0; row < a.length; row++) {
    a[row][column] = band[row]
  }
}
Enter fullscreen mode Exit fullscreen mode

Revealing my message - and counting 'on' lights

After storing the latest state of my grid as screen:

return screen.map(el => el.join('')).join('\n')
Enter fullscreen mode Exit fullscreen mode
  • Mutate each nested array into a string of its concatenated characters
  • Concatenate each string with a newline character
  • Print the resulting multi-line string

Replacing all .s with characters to better see the message:

####  ##   ##  ###   ##  ###  #  # #   # ##   ##  
#    #  # #  # #  # #  # #  # #  # #   ##  # #  # 
###  #  # #  # #  # #    #  # ####  # # #  # #  # 
#    #  # #### ###  # ## ###  #  #   #  #### #  # 
#    #  # #  # # #  #  # #    #  #   #  #  # #  # 
####  ##  #  # #  #  ### #    #  #   #  #  #  ##  
Enter fullscreen mode Exit fullscreen mode

Using another double-reduce() to count the # characters:

screen.reduce(
  (sum, line) => sum += line.reduce(
    (sum, character) => sum += character == '#' ? 1 : 0
  , 0)
, 0)
Enter fullscreen mode Exit fullscreen mode

It counted 128 lights on.

That was the correct answer!

Part 2

  • I already revealed my message!
  • And it was the correct answer!

Building a simulator to show each instruction

I created a new array called snapshots:

let snapshots = []
Enter fullscreen mode Exit fullscreen mode

In each of the three for loops within my three conditions, I inserted a statement that added a stringified snapshot of the grid at each state - after a rectangular area was turned on, and after each single rotation.

By the time my reduce() was done running, snapshots would be full of strings representing a sort of stop-motion animation.

Indeed, it replays the instructions back as if in real-time!
Animating the message reveal

I did it!!

  • I solved both parts!
  • For the first time, I solved Part 2 before Part 1, since I technically saw the message before I counted the amount of lights on!
  • I built a simulator that replays the message reveal process like a stop-motion animation!
  • I remain undefeated for all message-revealing puzzles!

Top comments (0)