janAkali

joined 2 years ago
[โ€“] janAkali@lemmy.one 2 points 6 months ago* (last edited 6 months ago)

Nim

Runtime: 30-40 ms
I'm not very experienced with recursion and memoization, so this took me quite a while.

Edit: slightly better version

template splitNum(numStr: string): seq[int] =
  @[parseInt(numStr[0..<numStr.len div 2]), parseInt(numStr[numStr.len div 2..^1])]

template applyRule(stone: int): seq[int] =
  if stone == 0: @[1]
  else:
    let numStr = $stone
    if numStr.len mod 2 == 0: splitNum(numStr)
    else: @[stone * 2024]

proc memRule(st: int): seq[int] =
  var memo {.global.}: Table[int, seq[int]]
  if st in memo: return memo[st]
  result = st.applyRule
  memo[st] = result

proc countAfter(stone: int, targetBlinks: int): int =
  var memo {.global.}: Table[(int, int), int]
  if (stone,targetBlinks) in memo: return memo[(stone,targetBlinks)]

  if targetBlinks == 0: return 1
  for st in memRule(stone):
    result += st.countAfter(targetBlinks - 1)
  memo[(stone,targetBlinks)] = result

proc solve(input: string): AOCSolution[int, int] =
  for stone in input.split.map(parseInt):
    result.part1 += stone.countAfter(25)
    result.part2 += stone.countAfter(75)

Codeberg repo

[โ€“] janAkali@lemmy.one 3 points 6 months ago

Nim

As many others today, I've solved part 2 first and then fixed a 'bug' to solve part 1. =)

type Vec2 = tuple[x,y:int]
const Adjacent = [(x:1,y:0),(-1,0),(0,1),(0,-1)]

proc path(start: Vec2, grid: seq[string]): tuple[ends, trails: int] =
  var queue = @[@[start]]
  var endNodes: HashSet[Vec2]
  while queue.len > 0:
    let path = queue.pop()
    let head = path[^1]
    let c = grid[head.y][head.x]

    if c == '9':
      inc result.trails
      endNodes.incl head
      continue

    for d in Adjacent:
      let nd = (x:head.x + d.x, y:head.y + d.y)
      if nd.x < 0 or nd.y < 0 or nd.x > grid[0].high or nd.y > grid.high:
        continue
      if grid[nd.y][nd.x].ord - c.ord != 1: continue
      queue.add path & nd
  result.ends = endNodes.len

proc solve(input: string): AOCSolution[int, int] =
  let grid = input.splitLines()
  var trailstarts: seq[Vec2]

  for y, line in grid:
    for x, c in line:
      if c == '0':
        trailstarts.add (x,y)

  for start in trailstarts:
    let (ends, trails) = start.path(grid)
    result.part1 += ends
    result.part2 += trails

Codeberg Repo

[โ€“] janAkali@lemmy.one 2 points 6 months ago* (last edited 6 months ago)

Nim

Wrote ugly-ass code today, but it was surprisingly easy to debug and fast.

Solution:
Part 1: Parse data into a sequence of blocks and empty space like in example (I use -1 for empty space) and two indexes. First index goes 0 -> end, second index starts at the end. When we encounter empty space -> we use value from second index and decrement it (while skipping empty spaces). Repeat until both indexes meet at some point.

Part 2: Parse data into sequence of block objects and try to insert each data block into each empty space block before it. Somehow it all just worked without too many bugs.

Runtime (final version): 123 ms

type
  BlockKind = enum Data, Space
  Block = object
    size: int
    case kind: BlockKind
    of Data:
      index: int
    of Space:
      discard

func parseBlocks(input: string): tuple[blocks: seq[Block], id: int] =
  for i, c in input:
    let digit = c.ord - '0'.ord
    if i mod 2 == 0:
      result.blocks.add Block(kind: Data, size: digit, index: result.id)
      if i < input.high: inc result.id
    else:
      result.blocks.add Block(kind: Space, size: digit)

proc solve(input: string): AOCSolution[int, int] =
  block p1:
    var memBlocks = newSeqOfCap[int](100_000)

    var indBlock = 0
    for i, c in input:
      let digit = c.ord - '0'.ord
      if i mod 2 == 0:
        memBlocks.add (indBlock).repeat(digit)
        inc indBlock
      else:
        memBlocks.add -1.repeat(digit)

    var ind = 0
    var revInd = memBlocks.high
    while ind <= revInd:
      if memBlocks[ind] == -1:
        while memBlocks[revInd] == -1: dec revInd
        result.part1 += ind * memBlocks[revInd]
        dec revInd
      else:
        result.part1 += ind * memBlocks[ind]
      inc ind

  block p2:
    var (memBlocks, index) = parseBlocks(input)
    var revInd = memBlocks.high
    while revInd > 0:
      doAssert memBlocks[revInd].kind == Data

      var spaceInd = -1
      let blockSize = memBlocks[revInd].size
      for ind in 0..revInd:
        if memBlocks[ind].kind == Space and memBlocks[ind].size >= blockSize:
          spaceInd = ind; break

      if spaceInd != -1:
        let bSize = memBlocks[revInd].size
        let diffSize = memBlocks[spaceInd].size - bSize
        swap(memBlocks[spaceInd], memBlocks[revInd])
        if diffSize != 0:
          memBlocks[revInd].size = bSize
          memBlocks.insert(Block(kind: Space, size: diffSize), spaceInd + 1)
          inc revInd # shift index bc we added object

      dec index
      # skip space blocks and data blocks with higher index
      while (dec revInd; revInd < 0 or
             memBlocks[revInd].kind != Data or
             memBlocks[revInd].index != index): discard

    var unitIndex = 0
    for b in memBlocks:
      case b.kind
      of Data:
        for _ in 1..b.size:
          result.part2 += unitIndex * b.index
          inc unitIndex
      of Space:
        unitIndex += b.size

Codeberg repo

[โ€“] janAkali@lemmy.one 4 points 6 months ago* (last edited 6 months ago) (1 children)

maybe Eric and co have begun to realise that family time is more precious than work time

Last year the difficulty was fluctuating from 0 to 100 each day.
This year all problems so far are suspiciously easy. Maybe the second half of the month will be extra hard?

[โ€“] janAkali@lemmy.one 1 points 6 months ago* (last edited 6 months ago)

Nim

Overall really simple puzzle, but description is so confusing, that I mostly solved it based on example diagrams.
Edit: much shorter and faster one-pass solution. Runtime: 132 us

type Vec2 = tuple[x,y: int]
func delta(a, b: Vec2): Vec2 = (a.x-b.x, a.y-b.y)
func outOfBounds[T: openarray | string](pos: Vec2, grid: seq[T]): bool =
  pos.x < 0 or pos.y < 0 or pos.x > grid[0].high or pos.y > grid.high

proc solve(input: string): AOCSolution[int, int] =
  var grid = input.splitLines()
  var antennas: Table[char, seq[Vec2]]

  for y, line in grid:
    for x, c in line:
      if c != '.':
        discard antennas.hasKeyOrPut(c, newSeq[Vec2]())
        antennas[c].add (x, y)

  var antinodesP1: HashSet[Vec2]
  var antinodesP2: HashSet[Vec2]

  for _, list in antennas:
    for ind, ant1 in list:
      antinodesP2.incl ant1 # each antenna is antinode
      for ant2 in list.toOpenArray(ind+1, list.high):
        let d = delta(ant1, ant2)
        for dir in [-1, 1]:
          var i = dir
          while true:
            let antinode = (x: ant1.x+d.x*i, y: ant1.y+d.y*i)
            if antinode.outOfBounds(grid): break
            if i in [1, -2]: antinodesP1.incl antinode
            antinodesP2.incl antinode
            i += dir
  result.part1 = antinodesP1.len
  result.part2 = antinodesP2.len


Codeberg repo

[โ€“] janAkali@lemmy.one 4 points 6 months ago (1 children)

Where have you seen a human that sits like this? ๐Ÿ™ƒ

[โ€“] janAkali@lemmy.one 2 points 6 months ago* (last edited 6 months ago)

Nim

Bruteforce, my beloved.

Wasted too much time on part 2 trying to make combinations iterator (it was very slow). In the end solved both parts with 3^n and toTernary.

Runtime: 1.5s

func digits(n: int): int =
  result = 1; var n = n
  while (n = n div 10; n) > 0: inc result

func concat(a: var int, b: int) =
  a = a * (10 ^ b.digits) + b

func toTernary(n: int, len: int): seq[int] =
  result = newSeq[int](len)
  if n == 0: return
  var n = n
  for i in 0..<len:
    result[i] = n mod 3
    n = n div 3

proc solve(input: string): AOCSolution[int, int] =
  for line in input.splitLines():
    let parts = line.split({':',' '})
    let res = parts[0].parseInt
    var values: seq[int]
    for i in 2..parts.high:
      values.add parts[i].parseInt

    let opsCount = values.len - 1
    var solvable = (p1: false, p2: false)
    for s in 0 ..< 3^opsCount:
      var sum = values[0]
      let ternary = s.toTernary(opsCount)
      for i, c in ternary:
        case c
        of 0: sum *= values[i+1]
        of 1: sum += values[i+1]
        of 2: sum.concat values[i+1]
        else: raiseAssert"!!"
      if sum == res:
        if ternary.count(2) == 0:
          solvable.p1 = true
        solvable.p2 = true
        if solvable == (true, true): break
    if solvable.p1: result.part1 += res
    if solvable.p2: result.part2 += res

Codeberg repo

[โ€“] janAkali@lemmy.one 2 points 6 months ago* (last edited 6 months ago)

Nim

Not the prettiest code, but it runs in 3 seconds. For part 2 I just place an obstacle at every position guard visited in part 1.

Edit: made step procedure more readable.

type
  Vec2 = tuple[x,y: int]
  Dir = enum
    Up, Right, Down, Left
  Guard = object
    pos: Vec2
    dir: Dir

proc step(guard: var Guard, map: seq[string]): bool =
  let next: Vec2 =
    case guard.dir
    of Up: (guard.pos.x, guard.pos.y-1)
    of Right: (guard.pos.x+1, guard.pos.y)
    of Down: (guard.pos.x, guard.pos.y+1)
    of Left: (guard.pos.x-1, guard.pos.y)

  if next.y < 0 or next.x < 0 or next.y > map.high or next.x > map[0].high:
    return false
  elif map[next.y][next.x] == '#':
    guard.dir = Dir((guard.dir.ord + 1) mod 4)
  else:
    guard.pos = next
  true

proc solve(input: string): AOCSolution[int, int] =
  var map = input.splitLines()
  var guardStart: Vec2
  block findGuard:
    for y, line in map:
      for x, c in line:
        if c == '^':
          guardStart = (x, y)
          map[y][x] = '.'
          break findGuard

  var visited: HashSet[Vec2]
  block p1:
    var guard = Guard(pos: guardStart, dir: Up)
    while true:
      visited.incl guard.pos
      if not guard.step(map): break
    result.part1 = visited.len

  block p2:
    for (x, y) in visited - [guardStart].toHashSet:
      var loopCond: HashSet[Guard]
      var guard = Guard(pos: guardStart, dir: Up)
      var map = map
      map[y][x] = '#'

      while true:
        loopCond.incl guard
        if not guard.step(map): break
        if guard in loopCond:
          inc result.part2
          break

Codeberg repo

[โ€“] janAkali@lemmy.one 2 points 6 months ago (1 children)

Good old bubble sort

[โ€“] janAkali@lemmy.one 0 points 6 months ago

You can put egregious amount of cheese on anything and it would taste atleast half as good as pizza.

[โ€“] janAkali@lemmy.one 6 points 6 months ago* (last edited 6 months ago) (1 children)

Nim

Solution: sort numbers using custom rules and compare if sorted == original. Part 2 is trivial.
Runtime for both parts: 1.05 ms

proc parseRules(input: string): Table[int, seq[int]] =
  for line in input.splitLines():
    let pair = line.split('|')
    let (a, b) = (pair[0].parseInt, pair[1].parseInt)
    discard result.hasKeyOrPut(a, newSeq[int]())
    result[a].add b

proc solve(input: string): AOCSolution[int, int] =
  let chunks = input.split("\n\n")
  let later = parseRules(chunks[0])
  for line in chunks[1].splitLines():
    let numbers = line.split(',').map(parseInt)
    let sorted = numbers.sorted(cmp =
      proc(a,b: int): int =
        if a in later and b in later[a]: -1
        elif b in later and a in later[b]: 1
        else: 0
    )
    if numbers == sorted:
      result.part1 += numbers[numbers.len div 2]
    else:
      result.part2 += sorted[sorted.len div 2]

Codeberg repo

[โ€“] janAkali@lemmy.one 4 points 6 months ago* (last edited 6 months ago)

Nim

Could be done more elegantly, but I havenโ€™t bothered yet.

proc solve(input: string): AOCSolution[int, int] =
  var lines = input.splitLines()

  block p1:
    # horiz
    for line in lines:
      for i in 0..line.high-3:
        if line[i..i+3] in ["XMAS", "SAMX"]:
          inc result.part1

    for y in 0..lines.high-3:
      #vert
      for x in 0..lines[0].high:
        let word = collect(for y in y..y+3: lines[y][x])
        if word in [@"XMAS", @"SAMX"]:
          inc result.part1

      #diag \
      for x in 0..lines[0].high-3:
        let word = collect(for d in 0..3: lines[y+d][x+d])
        if word in [@"XMAS", @"SAMX"]:
          inc result.part1

      #diag /
      for x in 3..lines[0].high:
        let word = collect(for d in 0..3: lines[y+d][x-d])
        if word in [@"XMAS", @"SAMX"]:
          inc result.part1

  block p2:
    for y in 0..lines.high-2:
      for x in 0..lines[0].high-2:
        let diagNW = collect(for d in 0..2: lines[y+d][x+d])
        let diagNE = collect(for d in 0..2: lines[y+d][x+2-d])
        if diagNW in [@"MAS", @"SAM"] and diagNE in [@"MAS", @"SAM"]:
          inc result.part2

Codeberg repo

view more: โ€น prev next โ€บ