Skip to content

Commit

Permalink
feat(2023-01): don't use string replacement to parse text
Browse files Browse the repository at this point in the history
"eightwo" should render as "82" not "8wo"

Solves part 2
  • Loading branch information
amclin committed Dec 5, 2023
1 parent cf077d8 commit 28efcb7
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 39 deletions.
100 changes: 85 additions & 15 deletions 2023/day-01/checksum.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,104 @@ const checksumLine = (data) => {
return parseInt(result)
}

const lazyNums = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
const lazyReg = new RegExp(lazyNums.join('|'))
const lazyNumsReversed = lazyNums.map((num) => num.split('').reverse().join(''))
const lazyReversReg = new RegExp(lazyNumsReversed.join('|'))

const lookupPosition = (match) => {
const idx = lazyNums.indexOf(match)
if (idx > 9) {
return idx - 9
}
return idx
}

const lookupPositionReversed = (match) => {
const reverseMatch = match.split('').reverse().join('')
const idx = lazyNums.indexOf(reverseMatch)
if (idx > 9) {
return idx - 9
}
return idx
}

const lazyChecksumLine = (data) => {
let first = ''
data.replace(lazyReg, (match) => {
first = lookupPosition(match)
return match // reinsert so we don't bork the data string
})
// find last matching digit by reversing the string and searching backwards
let last = ''
data = data.split('').reverse().join('')
data.replace(lazyReversReg, (match) => {
last = lookupPositionReversed(match)
return match // reinsert so we don't bork the data string
})

return parseInt(`${first}${last}`)
}

/**
* Generates the checksum for an entire set
* @param {array} set of lines containing data
*/
const checksumSet = (set) => {
const checksumSet = (set, sanitize = false, lazy = false) => {
let filter = (data) => data
if (sanitize) {
filter = sanitizeLine
}

let checksum = checksumLine
if (lazy) {
checksum = lazyChecksumLine
}

return set.reduce((total, current) => {
return total + checksumLine((current))
return total + checksum(
filter(current)
)
}, 0)
}

/**
* Generates the checksum for an entire set when data is not sanitized
* @param {array} set of lines containing data
*/
const checksumUnSanitizedSet = (set) => {
return set.reduce((total, current) => {
return total + checksumLine(sanitizeLine(current))
}, 0)
const numbers = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
const numbersReversed = numbers.map((num) => num.split('').reverse().join(''))
const reg = new RegExp(numbers.join('|'))
const regGlobal = new RegExp(numbers.join('|'), 'g')
const regReversed = new RegExp(numbersReversed.join('|'))

// Sanitizes using a single-pass regex replace all
// Produces 53885 which is incorrect for part 2
const byRegex = (data) => {
return data.replaceAll(regGlobal, (matched) => numbers.indexOf(matched) + 1)
}

// Sanitizes by replacing just the first and last text digits, in case shared letters is supposed to work
// Produces 53853 which is too low for part 2
const byFirstLast = (data) => {
// sanitize first matching digit
data = data.replace(reg, (matched) => numbers.indexOf(matched) + 1)
// sanitize last matching digit by reversing the string and searching backwards
data = data.split('').reverse().join('')
data = data.replace(regReversed, (matched) => numbersReversed.indexOf(matched) + 1)

// return original order
return data.split('').reverse().join('')
}

const numbers = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
const reg = new RegExp(numbers.join('|'), 'g')
/**
* Sanitzizes a line by replacing spelled-out numbers with data
* @param {string} data line of input to sanitize
*/
const sanitizeLine = (data) => {
return data.replaceAll(reg, (matched) => numbers.indexOf(matched))
const sanitizeLine = (data, method = 'byFirstLast') => {
const methods = {
none: (data) => data,
byFirstLast,
byRegex
}

return methods[method](data)
}

module.exports = { checksumLine, checksumSet, checksumUnSanitizedSet, sanitizeLine }
module.exports = { checksumLine, checksumSet, sanitizeLine, lazyChecksumLine }
123 changes: 101 additions & 22 deletions 2023/day-01/checksum.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-env mocha */
const { expect } = require('chai')
const { checksumSet, checksumUnSanitizedSet, checksumLine, sanitizeLine } = require('./checksum')
const { checksumSet, checksumLine, sanitizeLine, lazyChecksumLine } = require('./checksum')
const fs = require('fs')
const path = require('path')
const filePath = path.join(__dirname, 'input.txt')
Expand Down Expand Up @@ -30,7 +30,7 @@ describe('--- Day 1: Trebuchet?! ---', () => {
})
describe('Part 2', () => {
describe('sanitizeLine', () => {
const set = [
const data = [
'two1nine',
'eightwothree',
'abcone2threexyz',
Expand All @@ -41,40 +41,119 @@ describe('--- Day 1: Trebuchet?! ---', () => {
]
const result = [29, 83, 13, 24, 42, 14, 76]
it('cleans up a string when digits are spelled out', () => {
const set = JSON.parse(JSON.stringify(data))
for (let x = 0; x < set.length; x++) {
expect(checksumLine(sanitizeLine(set[x]))).to.equal(result[x])
// expect(checksumLine(sanitizeLine(set[x], 'sanitizeByRegex'))).to.equal(result[x])
// expect(checksumLine(sanitizeLine(set[x], 'sanitizeFirstLast'))).to.equal(result[x])
}
})
it('handles first matches, and doesn\'t allow for multiple words to share letters', () => {
expect(sanitizeLine('eightwothree')).to.equal('8wo3')
it('allows for skipping sanitation', () => {
const set = JSON.parse(JSON.stringify(data))
for (let x = 0; x < set.length; x++) {
expect(sanitizeLine(set[x], 'none')).to.equal(data[x])
}
})
})
describe('checksumUnSanitizedSet', () => {
it('calculates the checksum for a set of lines by summing the checksum of each sanitized line', () => {
const set = [
'two1nine',
'eightwothree',
'abcone2threexyz',
'xtwone3four',
'4nineeightseven2',
'zoneight234',
'7pqrstsixteen'
]
expect(checksumUnSanitizedSet(set)).to.equal(281)
describe('checksumSet', () => {
const data = [
'two1nine',
'eightwothree',
'abcone2threexyz',
'xtwone3four',
'4nineeightseven2',
'zoneight234',
'7pqrstsixteen'
]
it('can sanitize', () => {
expect(checksumSet(data, true)).to.equal(281)
})
})
describe('lazyChecksumLine', () => {
const data = [
'two1nine',
'eightwothree',
'abcone2threexyz',
'xtwone3four',
'4nineeightseven2',
'zoneight234',
'7pqrstsixteen'
]
const result = [29, 83, 13, 24, 42, 14, 76]
it('can match text or numeric for checksum calcs', () => {
const set = JSON.parse(JSON.stringify(data))
for (let x = 0; x < set.length; x++) {
expect(lazyChecksumLine(set[x])).to.equal(result[x])
}
})
})

// it('doesn`t sanitize by default', () => {
// const set = [
// 'two1nine',
// 'eightwothree',
// 'abcone2threexyz',
// 'xtwone3four',
// '4nineeightseven2',
// 'zoneight234',
// '7pqrstsixteen'
// ]
// expect(checksumSet(set)).to.be.NaN
// })
// it('allows for sanitizing to be explicitly disabled', () => {
// const set = [
// 'two1nine',
// 'eightwothree',
// 'abcone2threexyz',
// 'xtwone3four',
// '4nineeightseven2',
// 'zoneight234',
// '7pqrstsixteen'
// ]
// expect(checksumSet(set, 'none')).to.be.NaN
// })
// // it('calculates the checksum for a set of lines by summing the checksum of each line', () => {
// // const set = [
// // 'two1nine',
// // 'eightwothree',
// // 'abcone2threexyz',
// // 'xtwone3four',
// // '4nineeightseven2',
// // 'zoneight234',
// // '7pqrstsixteen'
// // ]
// // expect(checksumSet(set)).to.equal(281)
// // })
// })
describe('integeration', () => {
it('53853 is too low for part 2', (done) => {
let data
fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => {
let initData
before((done) => {
fs.readFile(filePath, { encoding: 'utf8' }, (err, rawData) => {
if (err) throw err
initData = inputToArray(initData.trim())
initData = inputToArray(rawData.trim())
// Deep copy to ensure we aren't mutating the original data
data = JSON.parse(JSON.stringify(initData))
expect(checksumUnSanitizedSet(data)).to.be.greaterThan(53853)
// data = JSON.parse(JSON.stringify(initData))
done()
})
})

it('is not done without sanitation, since that matches part 1', () => {
const data = JSON.parse(JSON.stringify(initData))
const result = checksumSet(data, true)
expect(result).to.not.equal(54953)
})

it('is not done with a one-time regex', () => {
const data = JSON.parse(JSON.stringify(initData))
const result = checksumSet(data, true)
expect(result).to.not.equal(53885) // result of one-time regex
})

it('is not done by sanitizing just the first and last strings', () => {
const data = JSON.parse(JSON.stringify(initData))
const result = checksumSet(data, true)
expect(result).to.not.equal(53853) // result of first/last substitution onlye
})
})
})
})
3 changes: 1 addition & 2 deletions 2023/day-01/solution.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ fs.readFile(filePath, { encoding: 'utf8' }, (err, initData) => {

const part2 = () => {
const data = resetInput()
console.debug(data)
return 'No answer yet'
return checksumSet(data, false, true)
}
const answers = []
answers.push(part1())
Expand Down

0 comments on commit 28efcb7

Please sign in to comment.