Skip to content

Latest commit

 

History

History
155 lines (124 loc) · 30.7 KB

16.md

File metadata and controls

155 lines (124 loc) · 30.7 KB

Day 16

Part 1

Calculate the sum of invalid IDs (not included in the first section of the puzzle input) in nearby tickets. What's the sum (= ticket scanning error rate)?

const input = `
class: 1-3 or 5-7
row: 6-11 or 33-44
seat: 13-40 or 45-50

your ticket:
7,1,14

nearby tickets:
7,3,47
40,4,50
55,2,20
38,6,12
`.trim()

const sections = input.split('\n\n')

const validIds = sections[0]
  .split('\n')
  .reduce((ranges, line) => {
    const [, value] = line.split(': ')
    ranges.push(...value.split(' or '))
    return ranges
  }, [])
  .reduce((ids, range) => {
    const [min, max] = range.split('-').map((str) => +str)
    ids.push(...Array.from({ length: max - min + 1 }, (_, i) => i + min))
    return ids
  }, [])

const idsInNearbyTickets = sections[2]
  .split('\n')
  .slice(1) // Skip header
  .reduce((ids, ticketFields) => {
    ids.push(...ticketFields.split(',').map((str) => +str))
    return ids
  }, [])

const ticketScanningErrorRate = idsInNearbyTickets
  .filter((id) => !validIds.includes(id))
  .reduce((sum, id) => sum + id)
console.log(ticketScanningErrorRate)

Try it out on flems.io

Part 2

Discard nearby tickets that contain invalid IDs. Use the remaining valid tickets to determine which field is which. Then look for the six fields on your ticket that start with the word departure. What's the result of multiplying those six values together?

A lazy-ass solution:

const sections = input.split('\n\n')

const fields = sections[0].split('\n').reduce((fields, line) => {
  const [name, ranges] = line.split(': ')
  const ids = ranges.split(' or ').reduce((ids, range) => {
    const [min, max] = range.split('-').map((str) => +str)
    ids.push(...Array.from({ length: max - min + 1 }, (_, i) => i + min))
    return ids
  }, [])
  fields[name] = ids
  return fields
}, {})

const validIds = Object.values(fields).reduce((validIds, ids) => {
  validIds.push(...ids)
  return validIds
}, [])

const yourTicket = sections[1]
  .split('\n')[1] // Skip header
  .split(',')
  .map(Number)

const nearbyTickets = sections[2]
  .split('\n')
  .slice(1) // Skip header
  .map((ticketFields) => ticketFields.split(',').map(Number))

const validNearbyTickets = nearbyTickets.filter((ids) =>
  ids.every((id) => validIds.includes(id))
)

const idsInTickets = validNearbyTickets.reduce(
  (ranges, ticket) => {
    ticket.forEach((id, i) => ranges[i].push(id))
    return ranges
  },
  yourTicket.map((id) => [id])
)

const possibleFieldIndexes = idsInTickets
  .map((fieldIds) =>
    Object.entries(fields)
      .filter(([, ids]) => fieldIds.every((id) => ids.includes(id)))
      .map(([name]) => name)
  )
  .reduce((acc, name, i) => {
    acc[i] = name
    return acc
  }, {})

const fieldIndexes = assignFieldIndexes(
  {},
  Object.entries(possibleFieldIndexes)
)
const result = Object.entries(fieldIndexes)
  .filter(([name]) => name.startsWith('departure'))
  .map(([, index]) => index)
  .reduce((acc, index) => acc * yourTicket[index], 1)
console.log(result)

function assignFieldIndexes(acc, entries) {
  if (entries.length === 0) return acc

  const index = entries.findIndex(([, fieldNames]) => fieldNames.length === 1)
  const [fieldIndex, fieldNames] = entries[index]
  const fieldName = fieldNames[0]

  acc[fieldName] = fieldIndex

  const newEntries = entries
    .map(([i, names]) => [i, names.filter((name) => name !== fieldName)])
    .filter(([, names]) => names.length !== 0)

  return assignFieldIndexes(acc, newEntries)
}

Put console.log()s after the intermediate variables to see how the flow of the code works. I'm too lazy to explain it (plus I already forgot and can't bother trying to understand my own code 😂).

flems

What did I learn?

Nothing. 🍭