Advent of Code, Day 4

Day 4 of Advent of Code is here! Check out Day 1 for what this is all about.

Did you join my leaderboard? I invite you to join my board, and we can see who can win it ;) Go to “Leaderboard” in the top nav and enter 1030369-e174b794 to join my board.

I have also loaded up the pure code on github, you can follow along there too: pretty pages or the repo.

Spoilers ahead!

I highly encourage you to get through the day’s challenge on your own. I would love to hear how you did! Did you find a better approach than I did?

Day 4: Passport Processing

Part I

We have a long list of passports:

“Passport data is validated in batch files (your puzzle input). Each passport is represented as a sequence of key:value pairs separated by spaces or newlines. Passports are separated by blank lines…Count the number of valid passports – those that have all required fields. Treat cid as optional. In your batch file, how many passports are valid?ref

Instead of a typical loading and loop sequence like I did in the earlier days, I want to normalize the data first. This will make it easier to work with. We can do some basic ETL first. ETL stands for extract, transform, and load. I am going to extract the data, transform it some, then load it into my tooling. I could loop it, and check for empty lines, and then deal with added complexity of any number of rows that all equal one passport, yuck. We’re better to just normalize the data and do a simpler loop.

Let’s Extract all that data

var data = window.document.getElementsByTagName("pre")[0].innerText

Now Transform it into what we want

var transformPassports = data.replace(/\n\n/ig,'LOZZI')
var noNewLines = transformPassports.replace(/\n/ig, ' ')
var passports = noNewLines.split('LOZZI')
  • since every two lines designates a passport, I am replacing all double lines, \n\n, with a value of my choice. Make sure to set it to a value that’s not in the string, LOZZI is not there
  • a single passport will span multiple lines, and I want it all on one line so we can do a cleaner loop. We can then replace a single line with a space.
  • finally, we break the data up into individual passports, splitting on the value we used in the first piece, LOZZI

Finally we Load it into our function and identify the valid passports

var counter = 0
passports.forEach(p => {
    var fields = p.trim().split(' ')
    if(fields.length === 8) {
        counter++
    } else if(fields.length === 7 && p.indexOf('cid') === -1){
        counter ++
    }
})
console.log(counter)

Here’s what I’m doing:

  • As we loop through the passports, I do a quick check to see if there are 8 fields, if so, we’re good!
  • If there aren’t 8 fields, then there could be 7, and if there is 7, the cid field is optional, so that’s the only field that could be missing.

Pretty straight forward!

Part II

“You can continue to ignore the cid field, but each other field has strict rules about what values are valid for automatic validation.” ref

Validation requirements have improved, and now fields have specific requirements on them, like birth year has to be four digits and between 1920 and 2002.

This one took longer, it was trickier with all the string comparisons and regex fun. I had the wrong answer a few times, got this nice error message a few times

Thankfully, this doesn’t impact collecting stars. Huge thanks to Connor for helping me through this one. Follow his advent as he writes lambda functions for each challenge.

Here we go:

var data = window.document.getElementsByTagName("pre")[0].innerText
var transformPassports = data.replace(/\n\n/ig,'LOZZI')
var noNewLines = transformPassports.replace(/\n/ig, ' ')
var passports = noNewLines.split('LOZZI')
  
var isValid = (field) => {
  var pair = field.split(':')
  var value = pair[1]
  
  switch (pair[0]) {
      case 'byr':
          if(value.length !== 4) return false
          if(Number(value) >= 1920 && Number(value) <= 2002) return true
          break
      case 'iyr':
          if(value.length !== 4) return false
          if(Number(value) >= 2010 && Number(value) <= 2020) return true
          break
      case 'eyr':
          if(value.length !== 4) return false
          if(Number(value) >= 2020 && Number(value) <= 2030) return true
          break
      case 'hgt':
          var height = value.match(/([0-9]*)(cm|in)/)
          if(height) {
              var measurement = height[2]
              var heightVal = height[1]
              if(measurement === 'cm') {
                  if(Number(heightVal) >= 150 && Number(heightVal) <= 193) return true
              }
              if(measurement === 'in') {
                  if(Number(heightVal) >= 59 && Number(heightVal) <= 76)return true
              }
          }
          break
      case 'hcl':
          if(value.match(/#[0-9a-fA-F]{6}/)) return true
          break
      case 'ecl':
          if(value.length !== 3) return false
          if(value.match(/amb|blu|brn|gry|grn|hzl|oth/)) return true
          break
      case 'pid':
          if(value.length !== 9) return false
          if(Number(value)) return true
          break
      case 'cid':
          return true
          break
      default:
          break
  }
  return false
}
  
var counter = 0
passports.forEach(p => {
    var fields = p.trim().split(' ')
    if(fields.length === 8 || (fields.length === 7 && p.indexOf('cid') === -1)){
        if(fields.every(f => isValid(f))) counter++
    }
})
console.log(counter)
  • Let’s start at the end, using the existing function we created in Part I
    • I consolidated the conditions into one for the sake of ease
    • Then we loop through the fields and call isValid() which is at the top of this code block
    • My headache: every() is VERY different than forEach(). I spend a lot of my time troubleshooting this one because I was expecting a return to work in forEach, but it doesn’t. every is the right method here. That expects that every item we loop through is true, if one is false, then it’s all false.
  • isValid() – I created a new function to check if a field is valid or not
    • Based on the field, specific requirements must be met.
    • I use a switch statement to check for the field name, then apply its requirements
    • A colleague asked “Why check for length if you’re comparing Numbers?”. Great question. I did it thinking about performance. Less code the merrier, faster code even better. I think a quick string.length is faster than converting that to Number then comparing. So if we know it’s not even the right length, it’s wrong.
    • For the height condition, you’ll see we’re using match() again, with a regular expression. This method is quickly growing on me for these challenges. It’s a great, fast, way to look for data in a string.
      • ([0-9]*) looks for any number
      • (cm|in) looks for a cm or an in value.
      • by wrapping both in ( ) that puts them into separate elements in the returned array
      • If a match is not found, it’s null
    • hcl is hair color, and it should be a hexidecimal value, like in CSS. Using another quick match()
      • #[0-9a-fA-F]{6} looks for a # and then any number between 0-9, and any letter between a-f (lower and upper case) and it must have 6 characters total (of characters in the [ ])
      • I could’ve done /#[0-9a-f]{6}/i, adding the i makes it case insensitive

How did you do?

How did you find the answer on your own? How did you do it? I’d love to hear how you did! Please comment below!

Series Navigation<< Adding your Advent of Code [AoC] Leaderboard to SlackAdvent of Code, Day 3 >>

Leave a Reply

Up ↑

Discover more from David Lozzi

Subscribe now to keep reading and get access to the full archive.

Continue reading