Skip to content

Latest commit

 

History

History
210 lines (170 loc) · 27.2 KB

04.md

File metadata and controls

210 lines (170 loc) · 27.2 KB

Day 04

Part 1

Validate the given passports: cid (Country ID) is optional, the other 7 fields are not.

Nothing fancy here; just splitting, trimming and some array methods:

const input = `
  ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
  byr:1937 iyr:2017 cid:147 hgt:183cm

  iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
  hcl:#cfa07d byr:1929

  hcl:#ae17e1 iyr:2013
  eyr:2024
  ecl:brn pid:760753108 byr:1931
  hgt:179cm

  hcl:#cfa07d eyr:2025 pid:166559648
  iyr:2011 ecl:brn hgt:59in
`.trim()

const passports = input.split('\n\n').map((passport) =>
  passport
    .trim()
    .split(/\s+/g)
    .map((field) => field.split(':'))
)

const requiredFields = ['byr', 'iyr', 'eyr', 'hgt', 'hcl', 'ecl', 'pid']

const validPassports = passports.filter((passport) => {
  const fields = passport.map(([key]) => key)
  return requiredFields.every((requiredField) => fields.includes(requiredField))
})

console.log(validPassports.length)

Try it out on flems.io

Part 2

Same as Part 1, but now the fields have specific requirements. cid (Country ID) is still optional and ignored.

First of all, let's modify each item in passports from e.g. [['byr', '1950'], ['hgt', '160cm']] to { byr: '1950', hgt: '160cm' }. This can be done with good ol' reduce():

const passports = input.split('\n\n').map((passport) =>
  passport
    .trim()
    .split(/\s+/g)
    .map((field) => field.split(':'))
    .reduce((acc, [key, value]) => {
      acc[key] = value
      return acc
    }, {})
)

I know I could have written the reducer like this:

const passports = input.split('\n\n').map((passport) =>
  passport
    .trim()
    .split(/\s+/g)
    .map((field) => field.split(':'))
    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
)

But I don't want to. It's uglier and slower. (The performance difference is negligible here. I know that too. :wink: Edit: I tested the performance difference using console.time() and console.timeEnd(), and it was about 10ms (on my machine), so not bad. But you'd better use the spread syntax carefully on large datasets.)

And now we can update the filtering with the required field validations. Because each item in passports is now an object, we can use destructuring and create human-readable aliases for the field names to improve readability:

const validPassports = passports.filter((passport) => {
  const {
    byr: birthYear,
    ecl: eyeColor,
    eyr: expirationYear,
    hcl: hairColor,
    hgt: height,
    iyr: issueYear,
    pid: passportId,
  } = passport

  if (!birthYear || birthYear < 1920 || birthYear > 2002) {
    return false
  }

  if (!issueYear || issueYear < 2010 || issueYear > 2020) {
    return false
  }

  if (!expirationYear || expirationYear < 2020 || expirationYear > 2030) {
    return false
  }

  if (!/^\d{9}$/.test(passportId)) {
    return false
  }

  if (!/^#[a-f\d]{6}$/.test(hairColor)) {
    return false
  }

  // 150-193cm or 59-76in
  if (!/^((1[5-8]\d|19[0-3])cm|(59|6\d|7[0-6])in)$/.test(height)) {
    return false
  }

  if (!['amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth'].includes(eyeColor)) {
    return false
  }

  return true
})

console.log(validPassports.length)

flems

The filtering method is a bit long, but straightforward and clear. It has lots of guard clauses – my favorite! I also aimed for symmetry, something I recently read about in Jason McCreary's book BaseCode. For example, the year checks come first, then the regex tests, then the single [].includes().

And look at that height validation! Just beautiful! I know, horrible!

At work we have a looong regex of whitelisted IP addresses. It looks similar, but is like 100× longer and more annoying to maintain. So while I could change the height validation to something more readable, I'm going to leave the regex version for your amusement.

What did I learn?

Like yesterday, I didn't learn anything new from doing today's puzzle. But I did learn something.

This repo doesn't (at least for now) contain any JS files, so I haven't bothered installing Prettier via npm. I also don't want to manually tweak formatting, so I installed the Prettier extension for VS Code. Now when I save a Markdown file (like this one which you are now reading), Prettier formats the JS code blocks too. Nice!

While reading the extension's documentation, I noticed it saying that it's not generally recommended to use ESLint to run Prettier (via eslint-plugin-prettier). I didn't know that. Prettier's Integrating with Linters page lists some downsides:

  • You end up with a lot of red squiggly lines in your editor, which gets annoying. Prettier is supposed to make you forget about formatting – and not be in your face about it!
  • They are slower than running Prettier directly.
  • They're yet one layer of indirection where things may break.

Makes sense. Guess who'll soon™ separate ESLint and Prettier in my blog and at work?