Skip to content

Commit 02204af

Browse files
committed
Added 2017 day 07
1 parent 8e7af30 commit 02204af

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

2017/07/code.mjs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import { delay, Console } from "../../utility.mjs";
2+
3+
export default class {
4+
/**
5+
* @param {Console} solConsole Solution console.
6+
* @param {HTMLElement} visContainer Visualization container.
7+
*/
8+
constructor(solConsole, visContainer) {
9+
this.isSolving = false;
10+
this.isStopping = false;
11+
this.solConsole = typeof solConsole !== "undefined" ? solConsole : new Console();
12+
this.visContainer = visContainer;
13+
}
14+
15+
/**
16+
* Parses the puzzle input.
17+
* @param {string} input Puzzle input.
18+
* @returns {Program[]} Programs.
19+
*/
20+
parse(input) {
21+
let consoleLine = this.solConsole.addLine("Parsing...");
22+
23+
let programs = [];
24+
input.trim().split(/\r?\n/).forEach((line, index) => {
25+
let match = line.match(/^([a-z]+) \((\d+)\)( -> ([a-z]+|, )+)?$/);
26+
if (match == null)
27+
throw new Error(`Invalid data in line ${index + 1}`);
28+
let program = programs.find(e => e.name == match[1]);
29+
if (program == undefined)
30+
programs.push(program = new Program(match[1]));
31+
program.weight = parseInt(match[2]);
32+
if (match[3] != undefined) {
33+
for (let childName of match[3].substring(4).split(", ")) {
34+
let childProgram = programs.find(e => e.name == childName);
35+
if (childProgram == undefined)
36+
programs.push(childProgram = new Program(childName));
37+
program.children.push(childProgram);
38+
childProgram.parent = program;
39+
}
40+
}
41+
});
42+
43+
consoleLine.innerHTML += " done.";
44+
return programs;
45+
}
46+
47+
48+
/**
49+
* Finds the name of the bottom program (part 1) or the correct weight of the program with the wrong weight (part 2).
50+
* @param {number} part Puzzle part.
51+
* @param {string} input Puzzle input.
52+
* @param {boolean} visualization Enable visualization.
53+
* @returns {string|number} Name of the bottom program (part 1) or the correct weight of the program with the wrong weight (part 2).
54+
*/
55+
async solve(part, input, visualization) {
56+
try {
57+
this.isSolving = true;
58+
59+
let programs = this.parse(input);
60+
61+
let visConsole = new Console();
62+
if (visualization)
63+
this.visContainer.append(visConsole.container);
64+
65+
let bottomPrograms = programs.filter(e => e.parent == null);
66+
if (bottomPrograms.length == 0)
67+
throw new Error("Bottom program not found.");
68+
if (bottomPrograms.length > 1)
69+
throw new Error("More than one bottom program found.");
70+
let bottomProgram = bottomPrograms[0];
71+
72+
if (part == 1) {
73+
if (visualization) {
74+
let drawTree = (program, prefix) => {
75+
visConsole.addLine(`${prefix}<span${prefix == "" ? " class='highlighted'" : ""}>${program.name}</span> (weight: ${program.weight})`);
76+
program.children.forEach(child => drawTree(child, prefix + " "));
77+
}
78+
drawTree(bottomProgram, "");
79+
}
80+
return bottomProgram.name;
81+
}
82+
else {
83+
bottomProgram.calculateTotalWeight();
84+
bottomProgram.calculateWeightChange(null);
85+
86+
if (bottomProgram.weightChange == null || bottomProgram.weightChange == 0)
87+
throw new Error("Solution not found");
88+
89+
if (visualization) {
90+
let drawTree = (program, prefix) => {
91+
if (program.weightChange == 0)
92+
visConsole.addLine(`${prefix}${program.name} (total weight: ${program.totalWeight})`);
93+
else if (program.children.some(e => e.weightChange != 0)) {
94+
visConsole.addLine(`${prefix}${program.name} (total weight: ${program.totalWeight}${program.weightChange > 0 ? "+" : "-"}${Math.abs(program.weightChange)}`
95+
+ `=${program.totalWeight + program.weightChange})`);
96+
program.children.forEach(child => drawTree(child, prefix + " "));
97+
}
98+
else {
99+
visConsole.addLine(`${prefix}${program.name} (total weight: <span class="highlighted">${program.weight}${program.weightChange > 0 ? "+" : "-"}${Math.abs(program.weightChange)}</span>`
100+
+ `+${program.totalWeight - program.weight}=${program.totalWeight + program.weightChange})`);
101+
}
102+
}
103+
drawTree(bottomProgram, "");
104+
}
105+
106+
let program = bottomProgram;
107+
for (; program.children.some(e => e.weightChange != 0); program = program.children.find(e => e.weightChange != 0));
108+
109+
return program.weight + program.weightChange;
110+
}
111+
}
112+
113+
finally {
114+
this.isSolving = false;
115+
}
116+
}
117+
118+
/**
119+
* Stops solving the puzzle.
120+
*/
121+
async stopSolving() {
122+
this.isStopping = true;
123+
while (this.isSolving)
124+
await(delay(10));
125+
this.isStopping = false;
126+
}
127+
}
128+
129+
/**
130+
* Puzzle program class.
131+
*/
132+
class Program {
133+
/**
134+
* @param {string} name Name.
135+
*/
136+
constructor(name) {
137+
/**
138+
* Name.
139+
* @type {string}
140+
*/
141+
this.name = name;
142+
/**
143+
* Weight.
144+
* @type {number}
145+
*/
146+
this.weight = 0;
147+
/**
148+
* Total weight (with children).
149+
* @type {number}
150+
*/
151+
this.totalWeight = 0;
152+
/**
153+
* Weight change (to balance the tower).
154+
* @type {number}
155+
*/
156+
this.weightChange = null;
157+
/**
158+
* Parent.
159+
* @type {Program}
160+
*/
161+
this.parent = null;
162+
/**
163+
* Children.
164+
* @type {Program[]}
165+
*/
166+
this.children = [];
167+
}
168+
169+
/**
170+
* Calculates the total weight of the program and its children.
171+
* @returns {number} Total weight.
172+
*/
173+
calculateTotalWeight() {
174+
this.totalWeight = this.weight + this.children.reduce((acc, e) => acc + e.calculateTotalWeight(), 0);
175+
return this.totalWeight;
176+
}
177+
178+
/**
179+
* Calculates the change of the weight of the program and its children to balance the tower.
180+
* @param {number} requiredWeightChange Required weight change (null if any weight change is ok).
181+
* @returns {number} Weight change (null if not possible).
182+
*/
183+
calculateWeightChange(requiredWeightChange) {
184+
// If no children: apply the required weight change to the current program
185+
if (this.children.length == 0)
186+
return this.weightChange = requiredWeightChange == null ? 0 : requiredWeightChange;
187+
// If 1 child:
188+
else if (this.children.length == 1) {
189+
let child = this.children[0];
190+
// If the child can be left unchanged or it's total weight can be changed by the required weight change: apply the required weight change to the current program
191+
if (child.calculateWeightChange(0) == 0 || child.calculateWeightChange(requiredWeightChange) != null)
192+
return this.weightChange = requiredWeightChange == null ? 0 : requiredWeightChange;
193+
else
194+
return this.weightChange = null;
195+
}
196+
// If more than 1 child:
197+
else {
198+
this.weightChange = null;
199+
// For every child:
200+
for (let childIndex = 0; childIndex < this.children.length && this.weightChange == null; childIndex++) {
201+
let child = this.children[childIndex];
202+
let otherChildren = this.children.filter((e, i) => i != childIndex);
203+
let uniqueOtherChildrenTotalWeights = otherChildren.map(e => e.totalWeight).filter((e, i, arr) => arr.indexOf(e) == i);
204+
// If other children have the same weight:
205+
if (uniqueOtherChildrenTotalWeights.length == 1) {
206+
let requiredChildWeightChange = uniqueOtherChildrenTotalWeights[0] - child.totalWeight;
207+
// If other children can be left unchanged and current child can be changed to have the same weight as others:
208+
if (otherChildren.every(e => e.calculateWeightChange(0) == 0) && child.calculateWeightChange(requiredChildWeightChange) != null) {
209+
// If required weight change is not specified: apply the required child weight change to the current program
210+
if (requiredWeightChange == null)
211+
return this.weightChange = requiredChildWeightChange;
212+
// If required child weight change is 0 or is the same as required weight change: apply the required weight change to the current program
213+
if (requiredChildWeightChange == 0 || requiredChildWeightChange == requiredWeightChange)
214+
return this.weightChange = requiredWeightChange;
215+
}
216+
}
217+
}
218+
219+
return this.weightChange;
220+
}
221+
}
222+
}

2017/07/testInput.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pbga (66)
2+
xhth (57)
3+
ebii (61)
4+
havc (66)
5+
ktlj (57)
6+
fwft (72) -> ktlj, cntj, xhth
7+
qoyq (66)
8+
padx (45) -> pbga, havc, qoyq
9+
tknk (41) -> ugml, padx, fwft
10+
jptl (61)
11+
ugml (68) -> gyxo, ebii, jptl
12+
gyxo (61)
13+
cntj (57)

tree.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,10 @@ export const years = [
422422
{
423423
name: "Day 6: Memory Reallocation", path: "./2017/06", taskUrl: "https://adventofcode.com/2017/day/6",
424424
answers: {part1: 5, part2: 4}
425+
},
426+
{
427+
name: "Day 7: Recursive Circus", path: "./2017/07", taskUrl: "https://adventofcode.com/2017/day/7",
428+
answers: {part1: "tknk", part2: 60}
425429
}
426430
]
427431
},

0 commit comments

Comments
 (0)