Created
December 19, 2016 01:01
-
-
Save pseale/f363425f08fa230236cdfa990768f220 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// DISCLAIMER: | |
// | |
// I'm getting lazier as I power through more and more of these Advent of Code challenges. | |
// Notably, I couldn't muster the energy to: | |
// * fix naming (e.g. part A is named "parseA" and part B is named "count") | |
// * Write part B tests | |
// * Write fine-grained unit tests | |
// * I probably left dead code in here | |
// | |
// Just know that I'm probably ashamed of some of this, but my laziness exceeds my fear of shame. | |
// | |
// Enjoy. | |
//--------------------------------------- | |
// tests - for Part A only - no Part B tests because motivation was very low | |
import parseA = require("../parseA") | |
describe("Acceptance tests", () => { | |
describe("Part A", () => { | |
test("Text with no markers should be kept verbatim", () => { | |
const result = parseA("ADVENT") | |
expect(result).toBe("ADVENT") | |
}) | |
test("Text with any markers should repeat the following text", () => { | |
const result = parseA("A(1x5)BC") | |
expect(result).toBe("ABBBBBC") | |
}) | |
test("Text within the data section of a marker is not parsed for markers", () => { | |
const result = parseA("X(8x2)(3x3)ABCY") | |
expect(result).toBe("X(3x3)ABC(3x3)ABCY") | |
}) | |
}) | |
}) | |
//--------------------------------------- | |
// Part A | |
interface ParseCompressionMarkerResult { | |
output: string[], | |
tokens: string[] | |
} | |
function parseCompressionMarker(tokens: string[]): ParseCompressionMarkerResult { | |
let tokenString = tokens.join("") | |
const match = /^\((\d+)x(\d+)\)/.exec(tokenString) | |
const characters = Number(match[1]) | |
const repeat = Number(match[2]) | |
const dataSectionPlusRemainingLine = tokenString.substr(match[0].length) | |
const dataSection = dataSectionPlusRemainingLine.substr(0, characters) | |
const remainingLine = dataSectionPlusRemainingLine.substr(characters) | |
return { | |
output: Array(repeat + 1).join(dataSection).split(""), | |
tokens: remainingLine.split("") | |
} | |
} | |
function parse(line: string): string { | |
let output = [] | |
let tokens = line.split("") | |
let numTokens = [] | |
let subsequentCharacters = 0 | |
while (_(tokens).some()) { | |
if (tokens[0] === "(") { | |
const result = parseCompressionMarker(tokens) | |
output = output.concat(result.output) | |
tokens = result.tokens | |
} else { | |
output.push(tokens.shift()) | |
} | |
} | |
return output.join("") | |
} | |
export = parse | |
//--------------------------------------- | |
// Part B (a copy+paste+rewrite of Part A) | |
interface StandardToken { | |
length: number, | |
text: string | |
} | |
interface CompressionMarkerToken { | |
length: number, | |
dataSection: Array<StandardToken|CompressionMarkerToken> | |
} | |
interface ParseCompressionMarkerResult { | |
remaining: string, | |
token: CompressionMarkerToken | |
} | |
function count(tokens: Array<StandardToken|CompressionMarkerToken>): number { | |
if (tokens.length === 0) { | |
return 0 | |
} | |
return tokens | |
.map(token => token.length) | |
.reduce((previousValue, currentValue) => previousValue + currentValue, 0) | |
} | |
function parseCompressionMarker(text: string): ParseCompressionMarkerResult { | |
const match = /^\((\d+)x(\d+)\)/.exec(text) | |
const characters = Number(match[1]) | |
const repeat = Number(match[2]) | |
const dataSectionPlusRemainingLine = text.substr(match[0].length) | |
const dataSection = dataSectionPlusRemainingLine.substr(0, characters) | |
const dataSectionTokens = parse(dataSection) | |
const remaining = dataSectionPlusRemainingLine.substr(characters) | |
const token = { | |
length: repeat * count(dataSectionTokens), | |
dataSection: dataSectionTokens | |
} | |
return { | |
remaining, | |
token | |
} | |
} | |
function parse(text: string): Array<StandardToken|CompressionMarkerToken> { | |
if (!text || text.length === 0) { | |
return [] | |
} | |
const tokens = [] | |
let remaining = text | |
while (remaining.length > 0) { | |
if (remaining[0] !== "(") { | |
const matches = /^(.*?)(\(|$)/.exec(remaining) | |
const token = matches[1] | |
remaining = remaining.substr(token.length) | |
tokens.push({ | |
length: token.length, | |
text: token | |
}) | |
} else { | |
const result = parseCompressionMarker(remaining) | |
remaining = result.remaining | |
tokens.push(result.token) | |
} | |
} | |
return tokens | |
} | |
function parseAndCount(text: string): number { | |
const tokens = parse(text) | |
return count(tokens) | |
} | |
export = parseAndCount | |
//--------------------------------------- | |
// Main (Node console runner) | |
import fs = require("fs") | |
import parseA = require("./parseA") | |
import count = require("./count") | |
const input = fs.readFileSync("./input.txt", "utf8") | |
.split("\n") | |
.map(x => x.trim()) | |
.filter(x => x !== "") | |
const partACount = input | |
.map(line => parseA(line)) | |
.reduce((previousValue, currentValue) => previousValue + currentValue.length, 0) | |
const partBCount = input | |
.map(line => count(line)) | |
.reduce((previousValue, currentValue) => previousValue + currentValue, 0) | |
console.log(`Part A: total count of uncompressed data is ${partACount} for ${input.length} lines`) | |
console.log(`Part B: total count of uncompressed data is ${partBCount} for ${input.length} lines`) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment