janAkali
This is just another layer. Order in 8/2(2+2) is still not clear if you understand the division symbol correctly.
https://en.wikipedia.org/wiki/Order_of_operations#Mixed_division_and_multiplication
Both are right, depending on who you ask:
most people see:
8 / 2 * (2+2) = 16
math people see "juxtaposition" instead of multiplication, it has precedence over multiplication and division:
5 / 2a = 5 / (2 * a)
Math notation just sucks and is not standard in general, everyone just tries to avoid ambiguity like in this equation.
> zweihander
Nim
view code
type
AOCSolution[T,U] = tuple[part1: T, part2: U]
Vec3 = tuple[x,y,z: int]
Node = ref object
pos: Vec3
cid: int
proc dist(a,b: Vec3): float =
sqrt(float((a.x-b.x)^2 + (a.y-b.y)^2 + (a.z-b.z)^2))
proc solve(input: string, p1_limit: int): AOCSolution[int, int] =
let boxes = input.splitLines().mapIt:
let parts = it.split(',')
let pos = Vec3 (parseInt parts[0], parseInt parts[1], parseInt parts[2])
Node(pos: pos, cid: -1)
var dists: seq[(float, (Node, Node))]
for i in 0 .. boxes.high - 1:
for j in i+1 .. boxes.high:
dists.add (dist(boxes[i].pos, boxes[j].pos), (boxes[i], boxes[j]))
var curcuits: Table[int, HashSet[Node]]
var curcuitID = 0
dists.sort(cmp = proc(a,b: (float, (Node, Node))): int = cmp(a[0], b[0]))
for ind, (d, nodes) in dists:
var (a, b) = nodes
let (acid, bcid) = (a.cid, b.cid)
if acid == -1 and bcid == -1: # new curcuit
a.cid = curcuitID
b.cid = curcuitID
curcuits[curcuitId] = [a, b].toHashSet
inc curcuitID
elif bcid == -1: # add to a
b.cid = acid
curcuits[acid].incl b
elif acid == -1: # add to b
a.cid = bcid
curcuits[bcid].incl a
elif acid != bcid: # merge two curcuits
for node in curcuits[bcid]:
node.cid = acid
curcuits[acid].incl curcuits[bcid]
curcuits.del bcid
if ind+1 == p1_limit:
result.part1 = curcuits.values.toseq.map(len).sorted()[^3..^1].prod
if not(acid == bcid and acid != -1): result.part2 = a.pos.x * b.pos.x
Runtime: 364 ms
Part 1:
I compute all pairs of Euclidean distances between 3D points, sort them, then connect points into circuits, using, what I think is called a Union‑Find algorithm (circuits grow or merge). After exactly 1000 connections (including redundant ones), I take the three largest circuits and multiply their sizes.
Part 2:
While iterating through the sorted connections, I also calculate the product of each pair x‑coordinates. The last product is a result for part 2.
Problems I encountered while doing this puzzle:
- I've changed a dozen of data structures, before settled on curcuitIDs and
ref objects stored in a HashTable (~ 40 min) - I did a silly mistake of mutating the fields of an object and then using new fields as keys for the HashTable (~ 20 min)
- I am stil confused and don't understand why do elves count already connected junction boxes (~ 40 min, had to look it up, otherwise it would be a lot more)
Time to solve Part 1: 1 hour 56 minutes
Time to solve Part 2: 4 minutes
Full solution at Codeberg: solution.nim
Nim
Another simple one.
Part 1: count each time a beam crosses a splitter.
Part 2: keep count of how many particles are in each column in all universes
(e.g. with a simple 1d array), then sum.
Runtime: ~~116 μs~~ ~~95 µs~~ 86 µs
old version
type
AOCSolution[T,U] = tuple[part1: T, part2: U]
proc solve(input: string): AOCSolution[int, int] =
var beams = newSeq[int](input.find '\n')
beams[input.find 'S'] = 1
for line in input.splitLines():
var newBeams = newSeq[int](beams.len)
for pos, cnt in beams:
if cnt == 0: continue
if line[pos] == '^':
newBeams[pos-1] += cnt
newBeams[pos+1] += cnt
inc result.part1
else:
newbeams[pos] += cnt
beams = newBeams
result.part2 = beams.sum()
Update: found even smaller and faster version that only needs a single array.
Update #2: small optimization
type
AOCSolution[T,U] = tuple[part1: T, part2: U]
proc solve(input: string): AOCSolution[int, int] =
var beams = newSeq[int](input.find '\n')
beams[input.find 'S'] = 1
for line in input.splitLines():
for pos, c in line:
if c == '^' and beams[pos] > 0:
inc result.part1
beams[pos-1] += beams[pos]
beams[pos+1] += beams[pos]
beams[pos] = 0
result.part2 = beams.sum()
Full solution at Codeberg: solution.nim
Nim
The hardest part was reading the part 2 description. I literally looked at it for minutes trying to understand where the problem numbers come from and how they're related to the example input. But then it clicked.
The next roadblock was that my template was stripping whitespace at the end of the last line, making parsing a lot harder. I've replaced strip() with strip(chars={'\n'}) to keep the trailing space intact.
Runtime: ~~1.4 ms~~ 618 μs
view code
type
AOCSolution[T,U] = tuple[part1: T, part2: U]
proc solve(input: string): AOCSolution[int, int] =
let lines = input.splitLines()
let numbers = lines[0..^2]
let ops = lines[^1]
block p1:
let numbers = numbers.mapIt(it.splitWhiteSpace().mapIt(parseInt it))
let ops = ops.splitWhitespace()
for x in 0 .. numbers[0].high:
var res = numbers[0][x]
for y in 1 .. numbers.high:
case ops[x]
of "*": res *= numbers[y][x]
of "+": res += numbers[y][x]
result.part1 += res
block p2:
var problems: seq[(char, Slice[int])]
var ind = 0
while ind < ops.len:
let len = ops.skipWhile({' '}, ind+1)
problems.add (ops[ind], ind .. ind + len - (if ind+len < ops.high: 1 else: 0))
ind += len + 1
for (op, cols) in problems:
var res = 0
for x in cols:
var num = ""
for y in 0 .. numbers.high:
num &= numbers[y][x]
if res == 0:
res = parseInt num.strip
else:
case op
of '*': res *= parseInt num.strip
of '+': res += parseInt num.strip
else: discard
result.part2 += res
Full solution at Codeberg: solution.nim
+1 shitty superpower ideas
I should really start writing them down at this point.
Nim
Huh, I didn't expect two easy days in a row.
Part 1 is a range check. Part 2 is a range merge.
Runtime: ~720 µs
type
AOCSolution[T,U] = tuple[part1: T, part2: U]
proc merge[T](ranges: var seq[Slice[T]]) =
ranges.sort(cmp = proc(r1, r2: Slice[T]): int = cmp(r1.a, r2.a))
var merged = @[ranges[0]]
for range in ranges.toOpenArray(1, ranges.high):
if range.a <= merged[^1].b:
if range.b > merged[^1].b:
merged[^1].b = range.b
else:
merged.add range
ranges = merged
proc solve(input: string): AOCSolution[int, int] =
let chunks = input.split("\n\n")
var freshRanges = chunks[0].splitLines().mapIt:
let t = it.split('-'); t[0].parseInt .. t[1].parseInt
freshRanges.merge()
block p1:
let availableFood = chunks[1].splitLines().mapIt(parseInt it)
for food in availableFood:
for range in freshRanges:
if food in range:
inc result.part1
break
block p2:
for range in freshRanges:
result.part2 += range.b-range.a+1
Full solution at Codeberg: solution.nim
Nim
type
AOCSolution[T,U] = tuple[part1: T, part2: U]
Vec2 = tuple[x,y: int]
proc removePaper(rolls: var seq[string]): int =
var toRemove: seq[Vec2]
for y, line in rolls:
for x, c in line:
if c != '@': continue
var adjacent = 0
for (dx, dy) in [(-1,-1),(0,-1),(1,-1),
(-1, 0), (1, 0),
(-1, 1),(0, 1),(1, 1)]:
let pos: Vec2 = (x+dx, y+dy)
if pos.x < 0 or pos.x >= rolls[0].len or
pos.y < 0 or pos.y >= rolls.len: continue
if rolls[pos.y][pos.x] == '@': inc adjacent
if adjacent < 4:
inc result
toRemove.add (x, y)
for (x, y) in toRemove: rolls[y][x] = '.'
proc solve(input: string): AOCSolution[int, int] =
var rolls = input.splitLines()
result.part1 = rolls.removePaper()
result.part2 = result.part1
while (let cnt = rolls.removePaper(); result.part2 += cnt; cnt) > 0:
discard
Today was so easy, that I decided to solve it twice, just for fun. First is a 2D traversal (see above). And then I did a node graph solution in a few minutes (in repo below). Both run in ~27 ms.
It's a bit concerning, because a simple puzzle can only mean that tomorrow will be a nightmare. Good Luck everyone, we will need it.
Full solution is at Codeberg: solution.nim
No? Most of his stuff are terrible movies.
But, I am watching them for fucking around with cool ideas and crazy visual scenes. Couldn't care less about plot, characters, sometimes logic, etc.