Skip to content

Feat/2023 day 01 #242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 5, 2023
Merged
117 changes: 117 additions & 0 deletions 2023/day-01/checksum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* Generates a checksum for a string by concatenating
* the first and last digits found in the string
* @param {string} data of a single line
*/
const checksumLine = (data) => {
const parsed = data.replaceAll(/([^0-9])/g, '') // trim non-numeric characters
let result = ''
if (parsed.length === 1) { // some strings only have a single digit
result = `${parsed}${parsed}`
} else {
result = `${parsed[0]}${parsed[parsed.length - 1]}`
}
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, 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 + checksum(
filter(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('')
}

/**
* Sanitzizes a line by replacing spelled-out numbers with data
* @param {string} data line of input to sanitize
*/
const sanitizeLine = (data, method = 'byFirstLast') => {
const methods = {
none: (data) => data,
byFirstLast,
byRegex
}

return methods[method](data)
}

module.exports = { checksumLine, checksumSet, sanitizeLine, lazyChecksumLine }
122 changes: 122 additions & 0 deletions 2023/day-01/checksum.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* eslint-env mocha */
const { expect } = require('chai')
const { checksumSet, checksumLine, sanitizeLine, lazyChecksumLine } = require('./checksum')
const fs = require('fs')
const path = require('path')
const filePath = path.join(__dirname, 'input.txt')
const { inputToArray } = require('../../2018/inputParser')

describe('--- Day 1: Trebuchet?! ---', () => {
describe('Part 1', () => {
describe('checksum', () => {
it('calculates the checksum for a string by concatentating the first and last number', () => {
// provided
expect(checksumLine('1abc2')).to.equal(12)
expect(checksumLine('pqr3stu8vwx')).to.equal(38)
expect(checksumLine('a1b2c3d4e5f')).to.equal(15)
})
it('handles the edge case of a line with only a single digit', () => {
// provided
expect(checksumLine('treb7uchet')).to.equal(77)
})
})
describe('checksumSet', () => {
it('calculates the checksum for a set of lines by summing the checksum of each line', () => {
// provided
const set = ['1abc2', 'pqr3stu8vwx', 'a1b2c3d4e5f', 'treb7uchet']
expect(checksumSet(set)).to.equal(142)
})
})
})
describe('Part 2', () => {
describe('sanitizeLine', () => {
const data = [
'two1nine',
'eightwothree',
'abcone2threexyz',
'xtwone3four',
'4nineeightseven2',
'zoneight234',
'7pqrstsixteen'
]
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('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('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])
}
})
})

describe.skip('integeration', () => {
let initData
before((done) => {
fs.readFile(filePath, { encoding: 'utf8' }, (err, rawData) => {
if (err) throw err
initData = inputToArray(rawData.trim())
// Deep copy to ensure we aren't mutating the original data
// 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, false, 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, false, 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, false, true)
expect(result).to.not.equal(53853) // result of first/last substitution onlye
})
})
})
})
3 changes: 3 additions & 0 deletions 2023/day-01/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// eslint-disable-next-line no-unused-vars
const console = require('../helpers')
require('./solution')
Loading