@@ -2,23 +2,30 @@ import path from "node:path";
22import { readInputLineByLine } from "@utils/io" ;
33
44export async function part1 ( inputFile : string ) {
5- return await day10 ( inputFile , calcButtonPresses ) ;
5+ return await day10 ( inputFile , parseMachines , calcButtonPresses ) ;
66}
77
88export async function part2 ( inputFile : string ) {
9- return await day10 ( inputFile ) ;
9+ return await day10 ( inputFile , parseJoltageMachines , calcJoltagePresses ) ;
1010}
1111
12- async function day10 ( inputFile : string , calcFn ? : ( machines : Machine [ ] ) => number ) {
12+ async function day10 < T > ( inputFile : string , parseFn : ( lines : string [ ] ) => T , calcFn : ( parsed : T ) => number ) {
1313 const inputPath = path . join ( __dirname , inputFile ) ;
1414 const lines = await readInputLineByLine ( inputPath ) ;
15- const machines = lines . map ( parseMachine ) ;
16- return calcFn ?. ( machines ) ;
15+ const parsed = parseFn ( lines ) ;
16+ return calcFn ( parsed ) ;
1717}
1818
1919type Machine = { target : bigint ; buttons : bigint [ ] ; } ;
2020
21- function parseMachine ( line : string ) : Machine {
21+ function parseMachines ( lines : string [ ] ) : Machine [ ] {
22+ return lines
23+ . map ( line => line . trim ( ) )
24+ . filter ( line => line . length > 0 )
25+ . map ( parseMachineLine ) ;
26+ }
27+
28+ function parseMachineLine ( line : string ) : Machine {
2229 const diagramMatch = line . match ( / \[ ( [ . # ] + ) \] / ) ;
2330 const diagram = diagramMatch ! [ 1 ] ;
2431 let target = 0n ;
@@ -53,6 +60,39 @@ function calcButtonPresses(machines: Machine[]): number {
5360 return total ;
5461}
5562
63+ type JoltageMachine = { targets : number [ ] ; buttons : number [ ] [ ] ; } ;
64+
65+ function parseJoltageMachines ( lines : string [ ] ) : JoltageMachine [ ] {
66+ return lines
67+ . map ( line => line . trim ( ) )
68+ . filter ( line => line . length > 0 )
69+ . map ( parseJoltageLine ) ;
70+ }
71+
72+ function parseJoltageLine ( line : string ) : JoltageMachine {
73+ const targetMatch = line . match ( / \{ ( [ ^ } ] * ) \} / ) ;
74+ if ( ! targetMatch ) {
75+ throw new Error ( `Invalid line (missing joltage): ${ line } ` ) ;
76+ }
77+ const targets = targetMatch [ 1 ]
78+ . split ( ',' )
79+ . map ( item => Number . parseInt ( item . trim ( ) , 10 ) ) ;
80+
81+ const buttons : number [ ] [ ] = [ ] ;
82+ const buttonMatches = line . matchAll ( / \( ( [ ^ ) ] * ) \) / g) ;
83+ for ( const match of buttonMatches ) {
84+ const content = match [ 1 ] . trim ( ) ;
85+ if ( content . length === 0 ) continue ;
86+ const indices = content
87+ . split ( ',' )
88+ . map ( item => Number . parseInt ( item . trim ( ) , 10 ) )
89+ . filter ( Number . isFinite ) ;
90+ buttons . push ( indices ) ;
91+ }
92+
93+ return { targets, buttons} ;
94+ }
95+
5696function minButtonPresses ( target : bigint , buttons : bigint [ ] ) : number {
5797 if ( target === 0n ) return 0 ;
5898 if ( buttons . length === 0 ) return - 1 ;
@@ -101,3 +141,222 @@ function buildSubsetMinWeights(buttons: bigint[]): Map<bigint, number> {
101141 visit ( 0 , 0n , 0 ) ;
102142 return map ;
103143}
144+
145+ function calcJoltagePresses ( machines : JoltageMachine [ ] ) : number {
146+ let total = 0 ;
147+ for ( const machine of machines ) {
148+ total += minJoltagePresses ( machine . targets , machine . buttons ) ;
149+ }
150+ return total ;
151+ }
152+
153+ function minJoltagePresses ( targets : number [ ] , buttons : number [ ] [ ] ) : number {
154+ if ( targets . every ( value => value === 0 ) ) return 0 ;
155+
156+ const upperBounds = buttons . map ( ( indices ) => {
157+ if ( indices . length === 0 ) return 0 ;
158+ let bound = Number . POSITIVE_INFINITY ;
159+ for ( const idx of indices ) {
160+ bound = Math . min ( bound , targets [ idx ] ) ;
161+ }
162+ return Number . isFinite ( bound ) ? bound : 0 ;
163+ } ) ;
164+
165+ const keep = upperBounds
166+ . map ( ( bound , idx ) => ( { bound, idx} ) )
167+ . filter ( item => item . bound > 0 ) ;
168+
169+ if ( keep . length === 0 ) return - 1 ;
170+
171+ const keptButtons = keep . map ( item => buttons [ item . idx ] ) ;
172+ const keptBounds = keep . map ( item => item . bound ) ;
173+
174+ const rowCount = targets . length ;
175+ const colCount = keptButtons . length ;
176+
177+ const matrix : Fraction [ ] [ ] = Array . from ( { length : rowCount } , ( ) =>
178+ Array . from ( { length : colCount } , ( ) => fracFromInt ( 0 ) )
179+ ) ;
180+
181+ keptButtons . forEach ( ( indices , col ) => {
182+ for ( const row of indices ) {
183+ matrix [ row ] [ col ] = fracFromInt ( 1 ) ;
184+ }
185+ } ) ;
186+
187+ const rhs = targets . map ( fracFromInt ) ;
188+ const { pivotCols, rrefMatrix, rrefRhs, inconsistent} = rrefSystem ( matrix , rhs ) ;
189+ if ( inconsistent ) return - 1 ;
190+
191+ const freeCols : number [ ] = [ ] ;
192+ for ( let col = 0 ; col < colCount ; col ++ ) {
193+ if ( ! pivotCols . includes ( col ) ) freeCols . push ( col ) ;
194+ }
195+
196+ const freeOrder = freeCols
197+ . map ( col => ( { col, bound : keptBounds [ col ] } ) )
198+ . sort ( ( a , b ) => a . bound - b . bound ) ;
199+
200+ const freeColsOrdered = freeOrder . map ( item => item . col ) ;
201+ const freeBounds = freeOrder . map ( item => item . bound ) ;
202+
203+ const pivotRows = new Map < number , number > ( ) ;
204+ pivotCols . forEach ( ( col , idx ) => {
205+ pivotRows . set ( col , idx ) ;
206+ } ) ;
207+
208+ const pivotInfos = pivotCols . map ( ( col ) => {
209+ const row = pivotRows . get ( col ) ! ;
210+ const coeffs = freeColsOrdered . map ( freeCol => rrefMatrix [ row ] [ freeCol ] ) ;
211+ return {
212+ col,
213+ rhs : rrefRhs [ row ] ,
214+ coeffs
215+ } ;
216+ } ) ;
217+
218+ let best = Number . POSITIVE_INFINITY ;
219+ const freeValues = new Array < number > ( freeColsOrdered . length ) . fill ( 0 ) ;
220+
221+ const evaluate = ( ) => {
222+ let sum = 0 ;
223+ for ( const value of freeValues ) sum += value ;
224+ if ( sum >= best ) return ;
225+
226+ for ( const pivot of pivotInfos ) {
227+ let value = pivot . rhs ;
228+ for ( let i = 0 ; i < freeValues . length ; i ++ ) {
229+ const coeff = pivot . coeffs [ i ] ;
230+ if ( coeff . num === 0n || freeValues [ i ] === 0 ) continue ;
231+ value = fracSub ( value , fracMulInt ( coeff , freeValues [ i ] ) ) ;
232+ }
233+ if ( value . num % value . den !== 0n ) return ;
234+ const pivotValue = Number ( value . num / value . den ) ;
235+ if ( pivotValue < 0 ) return ;
236+ if ( pivotValue > keptBounds [ pivot . col ] ) return ;
237+ sum += pivotValue ;
238+ if ( sum >= best ) return ;
239+ }
240+ best = Math . min ( best , sum ) ;
241+ } ;
242+
243+ const dfs = ( index : number , sum : number ) => {
244+ if ( sum >= best ) return ;
245+ if ( index === freeValues . length ) {
246+ evaluate ( ) ;
247+ return ;
248+ }
249+ const bound = freeBounds [ index ] ;
250+ for ( let value = 0 ; value <= bound ; value ++ ) {
251+ freeValues [ index ] = value ;
252+ dfs ( index + 1 , sum + value ) ;
253+ }
254+ } ;
255+
256+ dfs ( 0 , 0 ) ;
257+ return Number . isFinite ( best ) ? best : - 1 ;
258+ }
259+
260+ type Fraction = { num : bigint ; den : bigint } ;
261+
262+ function fracFromInt ( value : number ) : Fraction {
263+ return { num : BigInt ( value ) , den : 1n } ;
264+ }
265+
266+ function fracNormalize ( num : bigint , den : bigint ) : Fraction {
267+ if ( num === 0n ) return { num : 0n , den : 1n } ;
268+ if ( den < 0n ) {
269+ num = - num ;
270+ den = - den ;
271+ }
272+ const g = gcdBigInt ( num < 0n ? - num : num , den ) ;
273+ return { num : num / g , den : den / g } ;
274+ }
275+
276+ function fracAdd ( a : Fraction , b : Fraction ) : Fraction {
277+ return fracNormalize ( a . num * b . den + b . num * a . den , a . den * b . den ) ;
278+ }
279+
280+ function fracSub ( a : Fraction , b : Fraction ) : Fraction {
281+ return fracNormalize ( a . num * b . den - b . num * a . den , a . den * b . den ) ;
282+ }
283+
284+ function fracMul ( a : Fraction , b : Fraction ) : Fraction {
285+ return fracNormalize ( a . num * b . num , a . den * b . den ) ;
286+ }
287+
288+ function fracMulInt ( a : Fraction , value : number ) : Fraction {
289+ return fracNormalize ( a . num * BigInt ( value ) , a . den ) ;
290+ }
291+
292+ function fracDiv ( a : Fraction , b : Fraction ) : Fraction {
293+ if ( b . num === 0n ) throw new Error ( "Division by zero fraction" ) ;
294+ return fracNormalize ( a . num * b . den , a . den * b . num ) ;
295+ }
296+
297+ function gcdBigInt ( a : bigint , b : bigint ) : bigint {
298+ let x = a ;
299+ let y = b ;
300+ while ( y !== 0n ) {
301+ const t = x % y ;
302+ x = y ;
303+ y = t ;
304+ }
305+ return x ;
306+ }
307+
308+ function rrefSystem ( matrix : Fraction [ ] [ ] , rhs : Fraction [ ] ) {
309+ const rowCount = matrix . length ;
310+ const colCount = matrix [ 0 ] ?. length ?? 0 ;
311+ let row = 0 ;
312+ const pivotCols : number [ ] = [ ] ;
313+
314+ for ( let col = 0 ; col < colCount && row < rowCount ; col ++ ) {
315+ let pivot = row ;
316+ while ( pivot < rowCount && matrix [ pivot ] [ col ] . num === 0n ) {
317+ pivot ++ ;
318+ }
319+ if ( pivot === rowCount ) continue ;
320+
321+ if ( pivot !== row ) {
322+ [ matrix [ pivot ] , matrix [ row ] ] = [ matrix [ row ] , matrix [ pivot ] ] ;
323+ [ rhs [ pivot ] , rhs [ row ] ] = [ rhs [ row ] , rhs [ pivot ] ] ;
324+ }
325+
326+ const pivotVal = matrix [ row ] [ col ] ;
327+ for ( let j = col ; j < colCount ; j ++ ) {
328+ matrix [ row ] [ j ] = fracDiv ( matrix [ row ] [ j ] , pivotVal ) ;
329+ }
330+ rhs [ row ] = fracDiv ( rhs [ row ] , pivotVal ) ;
331+
332+ for ( let i = 0 ; i < rowCount ; i ++ ) {
333+ if ( i === row ) continue ;
334+ const factor = matrix [ i ] [ col ] ;
335+ if ( factor . num === 0n ) continue ;
336+ for ( let j = col ; j < colCount ; j ++ ) {
337+ matrix [ i ] [ j ] = fracSub ( matrix [ i ] [ j ] , fracMul ( factor , matrix [ row ] [ j ] ) ) ;
338+ }
339+ rhs [ i ] = fracSub ( rhs [ i ] , fracMul ( factor , rhs [ row ] ) ) ;
340+ }
341+
342+ pivotCols . push ( col ) ;
343+ row ++ ;
344+ }
345+
346+ let inconsistent = false ;
347+ for ( let i = 0 ; i < rowCount ; i ++ ) {
348+ let allZero = true ;
349+ for ( let j = 0 ; j < colCount ; j ++ ) {
350+ if ( matrix [ i ] [ j ] . num !== 0n ) {
351+ allZero = false ;
352+ break ;
353+ }
354+ }
355+ if ( allZero && rhs [ i ] . num !== 0n ) {
356+ inconsistent = true ;
357+ break ;
358+ }
359+ }
360+
361+ return { pivotCols, rrefMatrix : matrix , rrefRhs : rhs , inconsistent} ;
362+ }
0 commit comments