My solutions to 2018's advent of code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

day09.nim 3.0KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import strscans
  2. # For performance, we manually allocate a contiguous region of memory to store
  3. # all of our marbles in, and manually work out pointer offsets for them within
  4. # that region based on the marble ID. This is probably a very bad idea for
  5. # anything other than a performance-obsessed, short-lived program.
  6. type
  7. Marble = ptr object
  8. next: Marble
  9. const
  10. MarbleSize = sizeof(pointer)
  11. proc addressOf(memory: pointer, marbleNumber: int): Marble {.inline.} =
  12. cast[Marble](cast[uint](memory) + cast[uint](marbleNumber * MarbleSize))
  13. proc valueOf(memory: pointer, marble: Marble): int {.inline.} =
  14. cast[int](cast[uint](marble) - cast[uint](memory)) div MarbleSize
  15. proc insertAfter(node: Marble, memory: pointer, value: int): Marble {.inline.} =
  16. var newNode = memory.addressOf(value)
  17. newNode.next = node.next
  18. node.next = newNode
  19. newNode
  20. proc removeNext(node: Marble) {.inline.} =
  21. node.next = node.next.next
  22. proc newSingleNode(memory: pointer, value: int): Marble =
  23. result = memory.addressOf(value)
  24. result.next = result
  25. var
  26. input = readFile("data/09.txt")
  27. players: int
  28. marbles: int
  29. if not input.scanf("$i players; last marble is worth $i points", players, marbles):
  30. raise newException(Defect, "Invalid input line: " & input)
  31. # Instead of using a doubly-linked list, we keep a current pointer and a
  32. # separate one trailing behind it. The trail will expand to 8 marbles and is
  33. # used when a multiple of 23 is played (so we can remove the N-7th marble). The
  34. # trail then catches up to the current pointer and drifts back to 8 again over
  35. # the next few moves. This saves a 64 bit memory allocation per marble, which
  36. # gives a small but noticable speed bump.
  37. let
  38. hundredMarbles = marbles * 100
  39. memory = alloc(MarbleSize * hundredMarbles)
  40. var
  41. player = 0
  42. scores = newSeq[int](players)
  43. current = newSingleNode(memory, 0)
  44. currentTrail = current
  45. currentTrailDrift = 0
  46. specialCountdown = 23
  47. for i in 1 .. hundredMarbles:
  48. # Instead of testing each marble number to see if it's a multiple of 23, we
  49. # keep a counter that we just loop over and over again.
  50. specialCountdown.dec
  51. if specialCountdown == 0:
  52. # The current player is only relevant when a 23nth marble is played, so
  53. # we can just update the player here instead of every turn.
  54. player = (player + 23) mod players
  55. scores[player] += i + memory.valueOf(currentTrail.next)
  56. currentTrail.removeNext
  57. current = currentTrail.next
  58. currentTrail = current
  59. currentTrailDrift = 0
  60. specialCountdown = 23
  61. else:
  62. current = current.next.insertAfter(memory, i)
  63. if i > 8:
  64. if currentTrailDrift == 8:
  65. currentTrail = currentTrail.next.next
  66. else:
  67. currentTrailDrift += 2
  68. else:
  69. currentTrail = current
  70. if i == marbles or i == hundredMarbles:
  71. echo scores.max