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

  • 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




  • janAkali@lemmy.onetoAsklemmy@lemmy.mlHow did you find Lemmy?
    link
    fedilink
    English
    arrow-up
    12
    ·
    11 days ago

    I remember seeing lemmy maybe 4+ years ago on some open-source subreddit. It had practically non-existent user base, so I’ve ignored it. After that, I remember a first wave of people making mastodon accounts (even before elon). There I’ve first heard of concept of “fediverse”. I liked the idea but I honestly thought it had zero chances to compete with mainstream social media.

    And then everything turned to shit, making a gap between something like lemmy and reddit a lot smaller. So I’ve jumped the ship with everyone after the API shitstorm.