Skip to content

Commit 85bb2a2

Browse files
committedDec 25, 2023
✨ Add 2023 day 25 solutions
1 parent 752ddc5 commit 85bb2a2

File tree

3 files changed

+219
-0
lines changed

3 files changed

+219
-0
lines changed
 

‎2023/25/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Well this was harder than I'd expected. I had the right general idea but my execution was sloppy. I should have implemented proper pathfinding from the start, and I tripped up on a lot of silly bugs.
2+
3+
I overcomplicated my answer by pathing from every node to every node, when all that was needed a single node to every node. Possibly a little fragile, and it still runs slower than I'd like, but whatevs. Merry Christmas! 🎄
4+
5+
Rank #5779

‎2023/25/index.test.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from 'bun:test'
2+
import { getPart1Answer, part1Examples } from '.'
3+
import { logAnswer } from '@scripts/log'
4+
5+
const inputText = await Bun.file(`${import.meta.dir}/input.txt`).text()
6+
7+
test('solutions', () => {
8+
console.log(' 🌟 Part 1 answer:', getAndLogAnswer())
9+
part1Examples.forEach(([i, a]) => expect(`${getPart1Answer(i)}`).toBe(`${a}`))
10+
expect(1).toBe(1) // Ensures that console logs always run
11+
})
12+
13+
function getAndLogAnswer() {
14+
console.time(`P1`)
15+
const answer = `${getPart1Answer(inputText)}`
16+
console.timeEnd(`P1`)
17+
logAnswer(import.meta.dir, 1, answer)
18+
return answer
19+
}

‎2023/25/index.ts

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
const parseInput = (input: string) =>
2+
input
3+
.trim()
4+
.split('\n')
5+
.map((line) => {
6+
const [from, to] = line.split(': ')
7+
return { from, to: to.split(' ') }
8+
})
9+
10+
export const getPart1Answer: Answer = (input: string): string | number => {
11+
const parsed = parseInput(input)
12+
const components: Set<string> = new Set()
13+
const connections: Map<string, Set<string>> = new Map()
14+
const wires: Set<string> = new Set()
15+
for (const group of parsed) {
16+
components.add(group.from)
17+
const connectedSet = connections.get(group.from) || new Set()
18+
group.to.forEach((t) => connectedSet.add(t))
19+
connections.set(group.from, connectedSet)
20+
for (const to of group.to) {
21+
components.add(to)
22+
const connectedSet = connections.get(to) || new Set()
23+
connectedSet.add(group.from)
24+
connections.set(to, connectedSet)
25+
wires.add([group.from, to].sort().join('/'))
26+
}
27+
}
28+
function getGroup(origin: string, cuts: [string, string][]) {
29+
const group: Set<string> = new Set()
30+
const toCheck: Set<string> = new Set([origin])
31+
while (toCheck.size > 0 && group.size < components.size) {
32+
const [checking] = toCheck
33+
toCheck.delete(checking)
34+
group.add(checking)
35+
connections.get(checking)?.forEach((t) => {
36+
if (
37+
group.has(t) ||
38+
cuts.some(
39+
([c1, c2]) => (c1 === checking && c2 === t) || (c1 === t && c2 === checking)
40+
)
41+
)
42+
return
43+
toCheck.add(t)
44+
})
45+
}
46+
return group
47+
}
48+
type PathNode = {
49+
component: string
50+
dist: number
51+
parent: null | PathNode
52+
}
53+
54+
const pathCache: Map<string, string[]> = new Map()
55+
56+
function findWirePath(start: string, end: string) {
57+
const cacheKey = start < end ? `${start}>${end}` : `${end}>${start}`
58+
if (pathCache.has(cacheKey)) return pathCache.get(cacheKey)
59+
let current: PathNode = {
60+
component: start,
61+
dist: 0,
62+
parent: null,
63+
}
64+
const openNodes: Map<string, PathNode> = new Map([[start, current]])
65+
const closedNodes: Map<string, PathNode> = new Map()
66+
while (openNodes.size > 0) {
67+
closedNodes.set(current.component, current)
68+
openNodes.delete(current.component)
69+
if (current.component === end) {
70+
const path = constructPath(start, current)
71+
for (let i = 0; i < path.length - 1; i++) {
72+
const from = path[i]
73+
const cacheKey = from < end ? `${from}>${end}` : `${end}>${from}`
74+
pathCache.set(cacheKey, path.slice(i))
75+
}
76+
return path
77+
}
78+
const connectedTo = connections.get(current.component)!
79+
for (const connected of connectedTo) {
80+
if (closedNodes.has(connected)) continue
81+
const nextNode: PathNode = {
82+
parent: current,
83+
component: connected,
84+
dist: current.dist + 1,
85+
}
86+
const existing = openNodes.get(current.component)
87+
if (existing) {
88+
if (nextNode.dist < existing.dist) {
89+
existing.dist = nextNode.dist
90+
existing.parent = current
91+
}
92+
} else {
93+
openNodes.set(connected, nextNode)
94+
}
95+
}
96+
current = getBestNextNode(openNodes)
97+
}
98+
throw 'no path found'
99+
}
100+
101+
function getBestNextNode(nodes: Map<string, PathNode>) {
102+
let best: PathNode | undefined
103+
for (const [, value] of nodes) {
104+
if (!best || value.dist < best.dist) {
105+
best = value
106+
}
107+
}
108+
return best as PathNode
109+
}
110+
111+
function constructPath(start: string, end: PathNode) {
112+
const path = []
113+
let current = end
114+
let panic = 10000
115+
while (panic > 0) {
116+
panic--
117+
path.push(current.component)
118+
if (current.component === start) break
119+
current = current.parent!
120+
}
121+
return path.reverse()
122+
}
123+
124+
const wireKeys = [...wires]
125+
const wireUsage: Map<string, number> = new Map(wireKeys.map((w) => [w, 0]))
126+
const tested: Set<string> = new Set()
127+
const [fromComponent] = components
128+
for (const toComponent of components) {
129+
if (toComponent === fromComponent) continue
130+
const startComponent: string =
131+
fromComponent < toComponent ? fromComponent : toComponent
132+
const endComponent = startComponent === fromComponent ? toComponent : fromComponent
133+
const pairKey = `${startComponent}:${endComponent}`
134+
if (tested.has(pairKey)) continue
135+
tested.add(pairKey)
136+
const path = findWirePath(startComponent, endComponent)
137+
if (!path) throw 'no path!'
138+
for (let i = 1; i < path.length; i++) {
139+
const from = path[i - 1]
140+
const to = path[i]
141+
const wire = from < to ? `${from}/${to}` : `${to}/${from}`
142+
wireUsage.set(wire, wireUsage.get(wire)! + 1)
143+
}
144+
}
145+
const mostUsedWires = [...wireUsage]
146+
.sort((a, b) => b[1] - a[1])
147+
.map(([w]) => w)
148+
.slice(0, 40)
149+
const wireKeysArray = mostUsedWires.map((w) => w)
150+
const wiresArray = mostUsedWires.map((w) => w.split('/') as [string, string])
151+
const triedCuts: Set<string> = new Set()
152+
for (let i = 0; i < wireKeysArray.length; i++) {
153+
const cut1Key = wireKeysArray[i]
154+
const cut1Wires = wiresArray[i]
155+
for (let j = i + 1; j < wireKeysArray.length; j++) {
156+
if (j === i) continue
157+
const cut2Key = wireKeysArray[j]
158+
const cut2Wires = wiresArray[j]
159+
for (let k = j + 1; k < wireKeysArray.length; k++) {
160+
if (k === j || k === i) continue
161+
const cut3Key = wireKeysArray[k]
162+
const cutsKey = [cut1Key, cut2Key, cut3Key].sort().join('-')
163+
if (triedCuts.has(cutsKey)) continue
164+
triedCuts.add(cutsKey)
165+
const cut3Wires = wiresArray[k]
166+
const group1 = getGroup(cut1Wires[0], [cut1Wires, cut2Wires, cut3Wires])
167+
if (group1.size === components.size) continue
168+
const group2 = getGroup(cut1Wires[1], [cut1Wires, cut2Wires, cut3Wires])
169+
if (group1.size + group2.size === components.size) {
170+
return group1.size * group2.size
171+
}
172+
}
173+
}
174+
}
175+
throw 'no solution found'
176+
}
177+
178+
export const part1Examples: Example[] = [
179+
[
180+
`jqt: rhn xhk nvd
181+
rsh: frs pzl lsr
182+
xhk: hfx
183+
cmg: qnr nvd lhk bvb
184+
rhn: xhk bvb hfx
185+
bvb: xhk hfx
186+
pzl: lsr hfx nvd
187+
qnr: nvd
188+
ntq: jqt hfx bvb xhk
189+
nvd: lhk
190+
lsr: lhk
191+
rzs: qnr cmg lsr rsh
192+
frs: qnr lhk lsr`,
193+
'54',
194+
],
195+
]

0 commit comments

Comments
 (0)
Please sign in to comment.