aboutsummaryrefslogtreecommitdiffstats
path: root/day15/solution.nim
diff options
context:
space:
mode:
Diffstat (limited to 'day15/solution.nim')
-rw-r--r--day15/solution.nim117
1 files changed, 117 insertions, 0 deletions
diff --git a/day15/solution.nim b/day15/solution.nim
new file mode 100644
index 0000000..58e2aab
--- /dev/null
+++ b/day15/solution.nim
@@ -0,0 +1,117 @@
1import strutils
2import sequtils
3import re
4import sugar
5import algorithm
6import sets
7
8type
9 Point = tuple
10 x: int
11 y: int
12
13 Range = tuple
14 b: int
15 l: int
16
17 Entry = tuple
18 sensor: Point
19 beacon: Point
20 distance: int
21
22proc distance(A, B: Point): int =
23 result = abs(A.x - B.x) + abs(A.y - B.y)
24
25proc disjoinWith(A, B: Range): bool =
26 return A.b + A.l < B.b
27
28proc rangeContains(r: Range, y: int, p: Point): bool =
29 p.y == y and r.b <= p.x and p.x <= r.b + r.l - 1
30
31proc unite(A, B: Range): Range =
32 assert(A.b <= B.b)
33 assert(not A.disjoinWith(B))
34 result = (A.b, max(A.l, B.b - A.b + B.l))
35
36proc rangeComparator(A, B: Range): int =
37 if A.b < B.b or (A.b == B.b and A.l <= B.l):
38 return -1
39 else:
40 return 1
41
42proc parsePoint(strPoint: string): Point =
43 let tokens = strPoint.split(",")
44 Point((tokens[0].parseInt(), tokens[1].parseInt()))
45
46proc parseEntry(strEntry: string): Entry =
47 let tokens = strEntry.split(":")
48 let points = @[tokens[0].parsePoint(), tokens[1].parsePoint()]
49 Entry((points[0], points[1], distance(points[0], points[1])))
50
51proc parseFile(content: string): seq[Entry] =
52 let lines = content.strip().splitLines()
53
54 let strEntries = map(
55 lines,
56 (line) => line.replace(re"[^0-9\-,:]")
57 )
58
59 result = map(
60 strEntries,
61 parseEntry
62 )
63
64proc emptyRanges(entries: seq[Entry], criticalLine: int): seq[Range] =
65 var emptyRanges: seq[Range]
66 for entry in entries:
67 let
68 distFromLine = abs(entry.sensor.y - criticalLine)
69 rangeFromMid = entry.distance - distFromLine
70 b = entry.sensor.x - rangeFromMid
71 e = entry.sensor.x + rangeFromMid
72 l = e - b + 1
73 if l > 0:
74 emptyRanges.add((b, l))
75
76 emptyRanges.sort(rangeComparator)
77 var
78 curr = emptyRanges[0]
79 lastPushed = true
80
81 for other in emptyRanges[1..^1]:
82 lastPushed = false
83 if curr.disjoinWith(other):
84 result.add(curr)
85 lastPushed = true
86 curr = other
87 else:
88 curr = curr.unite(other)
89
90 if not lastPushed:
91 result.add(curr)
92
93proc part1(entries: seq[Entry], criticalLine = 2_000_000): int =
94 # List of all beacons
95 var beacons: HashSet[Point]
96 for entry in entries:
97 beacons.incl(entry.beacon)
98
99 let empty = emptyRanges(entries, criticalLine)
100 for r in empty:
101 result += r.l
102 for b in beacons:
103 if r.rangeContains(criticalLine, b):
104 result -= 1
105
106proc part2(entries: seq[Entry], criticalLength = 4_000_000): int =
107 for y in 0 .. criticalLength:
108 let empty = emptyRanges(entries, y)
109 if empty.len() == 2:
110 let x = empty[0].b + empty[0].l
111 return x * 4_000_000 + y
112
113let content = readFile("./input.txt")
114let entries = parseFile(content)
115
116echo part1(entries)
117echo part2(entries)