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+ }
0 commit comments