janAkali

  • 4 Posts
  • 233 Comments
Joined 2 years ago
cake
Cake day: June 6th, 2023

help-circle



  • Nim

    Very fiddly solution with lots of debugging required.

    Code
    type
      Vec2 = tuple[x,y: int]
      Box = array[2, Vec2]
      Dir = enum
        U = "^"
        R = ">"
        D = "v"
        L = "<"
    
    proc convertPart2(grid: seq[string]): seq[string] =
      for y in 0..grid.high:
        result.add ""
        for x in 0..grid[0].high:
          result[^1] &= (
            if grid[y][x] == 'O': "[]"
            elif grid[y][x] == '#': "##"
            else: "..")
    
    proc shiftLeft(grid: var seq[string], col: int, range: HSlice[int,int]) =
      for i in range.a ..< range.b:
        grid[col][i] = grid[col][i+1]
      grid[col][range.b] = '.'
    
    proc shiftRight(grid: var seq[string], col: int, range: HSlice[int,int]) =
      for i in countDown(range.b, range.a+1):
        grid[col][i] = grid[col][i-1]
      grid[col][range.a] = '.'
    
    proc box(pos: Vec2, grid: seq[string]): array[2, Vec2] =
      if grid[pos.y][pos.x] == '[':
        [pos, (pos.x+1, pos.y)]
      else:
        [(pos.x-1, pos.y), pos]
    
    proc step(grid: var seq[string], bot: var Vec2, dir: Dir) =
      var (x, y) = bot
      case dir
      of U:
        while (dec y; grid[y][x] != '#' and grid[y][x] != '.'): discard
        if grid[y][x] == '#': return
        if grid[bot.y-1][bot.x] == 'O': swap(grid[bot.y-1][bot.x], grid[y][x])
        dec bot.y
      of R:
        while (inc x; grid[y][x] != '#' and grid[y][x] != '.'): discard
        if grid[y][x] == '#': return
        if grid[bot.y][bot.x+1] == 'O': swap(grid[bot.y][bot.x+1], grid[y][x])
        inc bot.x
      of L:
        while (dec x; grid[y][x] != '#' and grid[y][x] != '.'): discard
        if grid[y][x] == '#': return
        if grid[bot.y][bot.x-1] == 'O': swap(grid[bot.y][bot.x-1], grid[y][x])
        dec bot.x
      of D:
        while (inc y; grid[y][x] != '#' and grid[y][x] != '.'): discard
        if grid[y][x] == '#': return
        if grid[bot.y+1][bot.x] == 'O': swap(grid[bot.y+1][bot.x], grid[y][x])
        inc bot.y
    
    proc canMoveVert(box: Box, grid: seq[string], boxes: var HashSet[Box], dy: int): bool =
      boxes.incl box
      var left, right = false
      let (lbox, rbox) = (box[0], box[1])
      let lbigBox = box((lbox.x, lbox.y+dy), grid)
      let rbigBox = box((rbox.x, lbox.y+dy), grid)
    
      if grid[lbox.y+dy][lbox.x] == '#' or
         grid[rbox.y+dy][rbox.x] == '#': return false
      elif grid[lbox.y+dy][lbox.x] == '.': left = true
      else:
        left = canMoveVert(box((lbox.x,lbox.y+dy), grid), grid, boxes, dy)
    
      if grid[rbox.y+dy][rbox.x] == '.': right = true
      elif lbigBox == rbigBox: right = left
      else:
        right = canMoveVert(box((rbox.x, rbox.y+dy), grid), grid, boxes, dy)
    
      left and right
    
    proc moveBoxes(grid: var seq[string], boxes: var HashSet[Box], d: Vec2) =
      for box in boxes:
        grid[box[0].y][box[0].x] = '.'
        grid[box[1].y][box[1].x] = '.'
      for box in boxes:
        grid[box[0].y+d.y][box[0].x+d.x] = '['
        grid[box[1].y+d.y][box[1].x+d.x] = ']'
      boxes.clear()
    
    proc step2(grid: var seq[string], bot: var Vec2, dir: Dir) =
      case dir
      of U:
        if grid[bot.y-1][bot.x] == '#': return
        if grid[bot.y-1][bot.x] == '.': dec bot.y
        else:
          var boxes: HashSet[Box]
          if canMoveVert(box((x:bot.x, y:bot.y-1), grid), grid, boxes, -1):
            grid.moveBoxes(boxes, (0, -1))
            dec bot.y
      of R:
        var (x, y) = bot
        while (inc x; grid[y][x] != '#' and grid[y][x] != '.'): discard
        if grid[y][x] == '#': return
        if grid[bot.y][bot.x+1] == '[': grid.shiftRight(bot.y, bot.x+1..x)
        inc bot.x
      of L:
        var (x, y) = bot
        while (dec x; grid[y][x] != '#' and grid[y][x] != '.'): discard
        if grid[y][x] == '#': return
        if grid[bot.y][bot.x-1] == ']': grid.shiftLeft(bot.y, x..bot.x-1)
        dec bot.x
      of D:
        if grid[bot.y+1][bot.x] == '#': return
        if grid[bot.y+1][bot.x] == '.': inc bot.y
        else:
          var boxes: HashSet[Box]
          if canMoveVert(box((x:bot.x, y:bot.y+1), grid), grid, boxes, 1):
            grid.moveBoxes(boxes, (0, 1))
            inc bot.y
    
    
    proc solve(input: string): AOCSolution[int, int] =
      let chunks = input.split("\n\n")
      var grid = chunks[0].splitLines()
      let movements = chunks[1].splitLines().join().join()
    
      var robot: Vec2
      for y in 0..grid.high:
        for x in 0..grid[0].high:
          if grid[y][x] == '@':
            grid[y][x] = '.'
            robot = (x,y)
    
      block p1:
        var grid = grid
        var robot = robot
        for m in movements:
          let dir = parseEnum[Dir]($m)
          step(grid, robot, dir)
        for y in 0..grid.high:
          for x in 0..grid[0].high:
            if grid[y][x] == 'O':
              result.part1 += 100 * y + x
    
      block p2:
        var grid = grid.convertPart2()
        var robot = (robot.x*2, robot.y)
        for m in movements:
          let dir = parseEnum[Dir]($m)
          step2(grid, robot, dir)
          #grid.inspect(robot)
    
        for y in 0..grid.high:
          for x in 0..grid[0].high:
            if grid[y][x] == '[':
              result.part2 += 100 * y + x
    

    Codeberg Repo


  • Nim

    Part 1: there’s no need to simulate each step, final position for each robot is
    (position + velocity * iterations) modulo grid
    Part 2: I solved it interactively: Maybe I just got lucky, but my input has certain pattern: after 99th iteration every 101st iteration looking very different from other. I printed first couple hundred iterations, noticed a pattern and started looking only at “interesting” grids. It took 7371 iterations (I only had to check 72 manually) to reach an easter egg.

    type
      Vec2 = tuple[x,y: int]
      Robot = object
        pos, vel: Vec2
    
    var
      GridRows = 101
      GridCols = 103
    
    proc examine(robots: seq[Robot]) =
      for y in 0..<GridCols:
        for x in 0..<GridRows:
          let c = robots.countIt(it.pos == (x, y))
          stdout.write if c == 0: '.' else: char('0'.ord + c)
        stdout.write '\n'
        stdout.flushFile()
    
    proc solve(input: string): AOCSolution[int, int] =
      var robots: seq[Robot]
      for line in input.splitLines():
        let parts = line.split({' ',',','='})
        robots.add Robot(pos: (parts[1].parseInt,parts[2].parseInt),
                         vel: (parts[4].parseInt,parts[5].parseInt))
    
      block p1:
        var quads: array[4, int]
        for robot in robots:
          let
            newX = (robot.pos.x + robot.vel.x * 100).euclmod GridRows
            newY = (robot.pos.y + robot.vel.y * 100).euclmod GridCols
            relRow = cmp(newX, GridRows div 2)
            relCol = cmp(newY, GridCols div 2)
          if relRow == 0 or relCol == 0: continue
          inc quads[int(relCol>0)*2 + int(relRow>0)]
    
        result.part1 = quads.foldl(a*b)
    
      block p2:
        if GridRows != 101: break p2
        var interesting = 99
        var interval = 101
    
        var i = 0
        while true:
          for robot in robots.mitems:
            robot.pos.x = (robot.pos.x + robot.vel.x).euclmod GridRows
            robot.pos.y = (robot.pos.y + robot.vel.y).euclmod GridCols
          inc i
    
          if i == interesting:
            robots.examine()
            echo "Iteration #", i, "; Do you see an xmas tree?[N/y]"
            if stdin.readLine().normalize() == "y":
              result.part2 = i
              break
            interesting += interval
    

    Codeberg Repo


  • Nim

    I’m embarrasingly bad with math. Couldn’t have solved this one without looking up the solution. =C

    type Vec2 = tuple[x,y: int64]
    
    const
      PriceA = 3
      PriceB = 1
      ErrorDelta = 10_000_000_000_000
    
    proc isInteger(n: float): bool = n.round().almostEqual(n)
    proc `+`(a: Vec2, b: int): Vec2 = (a.x + b, a.y + b)
    
    proc solveEquation(a, b, prize: Vec2): int =
      let res_a = (prize.x*b.y - prize.y*b.x) / (a.x*b.y - a.y*b.x)
      let res_b = (a.x*prize.y - a.y*prize.x) / (a.x*b.y - a.y*b.x)
      if res_a.isInteger and res_b.isInteger:
        res_a.int * PriceA + res_b.int * PriceB
      else: 0
    
    proc solve(input: string): AOCSolution[int, int] =
      let chunks = input.split("\n\n")
      for chunk in chunks:
        let lines = chunk.splitLines()
        let partsA = lines[0].split({' ', ',', '+'})
        let partsB = lines[1].split({' ', ',', '+'})
        let partsC = lines[2].split({' ', ',', '='})
    
        let a = (parseBiggestInt(partsA[3]), parseBiggestInt(partsA[6]))
        let b = (parseBiggestInt(partsB[3]), parseBiggestInt(partsB[6]))
        let c = (parseBiggestInt(partsC[2]), parseBiggestInt(partsC[5]))
    
        result.part1 += solveEquation(a,b,c)
        result.part2 += solveEquation(a,b,c+ErrorDelta)
    

  • Nim

    Runtime: 7ms 3.18 ms

    Part 1: I use flood fill to count all grouped plants and keep track of each border I see.
    Part 2: I use an algorithm similar to “merge overlapping ranges” to count spans of borders (border orientation matters) in each row and column, for each group. Resulting code (hidden under spoiler) is a little messy and not very DRY (it’s completely soaked).

    Edit: refactored solution, removed some very stupid code.

    proc groupSpans()
    proc groupSpans(borders: seq[(Vec2, Dir)]): int =
      ## returns number of continuous groups of cells with same Direction
      ## and on the same row or column
      var borders = borders
      var horiz = borders.filterIt(it[1] in {U, D})
      while horiz.len > 0:
        var sameYandDir = @[horiz.pop()]
        var curY = sameYandDir[^1][0].y
        var curDir = sameYandDir[^1][1]
        for i in countDown(horiz.high, 0):
          if horiz[i][0].y == curY and horiz[i][1] == curDir:
            sameYandDir.add horiz[i]
            horiz.del i
        sameYandDir.sort((a,b)=>cmp(a[0].x, b[0].x), Descending)
    
        var cnt = 1
        for i, (p,d) in sameYandDir.toOpenArray(1, sameYandDir.high):
          if sameYandDir[i][0].x - p.x  != 1: inc cnt
        result += cnt
    
      var vert = borders.filterIt(it[1] in {L, R})
      while vert.len > 0:
        var sameXandDir = @[vert.pop()]
        var curX = sameXandDir[^1][0].x
        var curDir = sameXandDir[^1][1]
        for i in countDown(vert.high, 0):
          if vert[i][0].x == curX and vert[i][1] == curDir:
            sameXandDir.add vert[i]
            vert.del i
        sameXandDir.sort((a,b)=>cmp(a[0].y, b[0].y), Descending)
    
        var cnt = 1
        for i, (p,d) in sameXandDir.toOpenArray(1, sameXandDir.high):
          if sameXandDir[i][0].y - p.y  != 1: inc cnt
        result += cnt
    
    type
      Dir = enum L,R,U,D
      Vec2 = tuple[x,y: int]
      GroupData = object
        plantCount: int
        borders: seq[(Vec2, Dir)]
    
    const Adjacent: array[4, Vec2] = [(-1,0),(1,0),(0,-1),(0,1)]
    
    proc solve(input: string): AOCSolution[int, int] =
      let grid = input.splitLines()
      var visited = newSeqWith(grid.len, newSeq[bool](grid[0].len))
      var groups: seq[GroupData]
    
      proc floodFill(pos: Vec2, plant: char, groupId: int) =
        visited[pos.y][pos.x] = true
        inc groups[groupId].plantCount
        for di, d in Adjacent:
          let pd: Vec2 = (pos.x+d.x, pos.y+d.y)
          if pd.x < 0 or pd.y < 0 or pd.x > grid[0].high or pd.y > grid.high or
            grid[pd.y][pd.x] != plant:
            groups[groupId].borders.add (pd, Dir(di))
            continue
          if visited[pd.y][pd.x]: continue
          floodFill(pd, plant, groupId)
    
      for y in 0..grid.high:
        for x in 0..grid[0].high:
          if visited[y][x]: continue
          groups.add GroupData()
          floodFill((x,y), grid[y][x], groups.high)
    
      for gid, group in groups:
        result.part1 += group.plantCount * group.borders.len
        result.part2 += group.plantCount * group.borders.groupSpans()
    

    Codeberg repo


  • 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


  • 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


  • 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



  • 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



  • 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


  • 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




  • 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


  • 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


  • Nim

    From a first glance it was obviously a regex problem.
    I’m using tinyre here instead of stdlib re library just because I’m more familiar with it.

    import pkg/tinyre
    
    proc solve(input: string): AOCSolution[int, int] =
      var allow = true
      for match in input.match(reG"mul\(\d+,\d+\)|do\(\)|don't\(\)"):
        if match == "do()": allow = true
        elif match == "don't()": allow = false
        else:
          let pair = match[4..^2].split(',')
          let mult = pair[0].parseInt * pair[1].parseInt
          result.part1 += mult
          if allow: result.part2 += mult
    

    Codeberg repo