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.

day20.nim 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import deques, sequtils, sets, strutils, tables
  2. type
  3. Point = tuple[x,y: int]
  4. Direction = enum
  5. N, E, S, W
  6. Sequence = ref object
  7. forks: bool
  8. nodes: seq[Sequence]
  9. content: seq[Direction]
  10. proc `+` (a, b: Point): Point =
  11. result.x = a.x + b.x
  12. result.y = a.y + b.y
  13. # Parses the regular expression into a 'Sequence' data structure, by pushing
  14. # sequences onto a stack each time a nested expression is encountered.
  15. #
  16. # Three types of sequences are found:
  17. # 1) "Content-only", e.g. NNNEEE
  18. # 2) "Forks", e.g. (subsequence|subsequence|subsequence)
  19. # 3) "Concatenations", e.g. NN(subsequence)EE
  20. #
  21. func process(regex: string): Sequence =
  22. var
  23. stack = initDeque[Sequence]()
  24. span: seq[Direction]
  25. result = new(Sequence)
  26. stack.addLast(result)
  27. for c in regex:
  28. if c == '^':
  29. continue
  30. if c in ['(', ')', '|', '$']:
  31. if span.len > 0:
  32. # If we've got bare directions, add them as a content element.
  33. var newSeq = new(Sequence)
  34. newSeq.content = span
  35. stack.peekLast.nodes.add(newSeq)
  36. span.setLen(0)
  37. if c == '(':
  38. # To start a new fork block we add two sequences: one that will
  39. # contain all the forked elements, and the first child to hold
  40. # the content of the first fork.
  41. var forkSeq = new(Sequence)
  42. forkSeq.forks = true
  43. stack.addLast(forkSeq)
  44. stack.addLast(new(Sequence))
  45. elif c == '|':
  46. # Pop the previous child off and add it to the parent fork block,
  47. # then add a new one for the next branch.
  48. var child = stack.popLast
  49. stack.peekLast.nodes.add(child)
  50. stack.addLast(new(Sequence))
  51. elif c == ')':
  52. # Pop both the child and the parent fork block off the stack.
  53. var child = stack.popLast
  54. stack.peekLast.nodes.add(child)
  55. var forkSeq = stack.popLast
  56. stack.peekLast.nodes.add(forkSeq)
  57. else:
  58. span.add(parseEnum[Direction]($c))
  59. stack.popLast
  60. let
  61. directions = {
  62. N: ( 0, 1),
  63. E: ( 1, 0),
  64. S: ( 0, -1),
  65. W: (-1, 0),
  66. }.toTable
  67. opposites = { N: S, E: W, S: N, W: E, }.toTable
  68. input = readFile("data/20.txt").strip
  69. proc addDirection(table: var Table[Point, HashSet[Direction]], point: Point, direction: Direction) =
  70. if not table.hasKey(point):
  71. table[point] = initSet[Direction]()
  72. table[point].incl(direction)
  73. proc addDirections(table: var Table[Point, HashSet[Direction]], point: Point, direction: Direction): Point =
  74. let otherPoint = point + directions[direction]
  75. table.addDirection(point, direction)
  76. table.addDirection(otherPoint, opposites[direction])
  77. otherPoint
  78. # Recurses through the given sequence and builds up a map of which points are
  79. # traversable in which directions.
  80. #
  81. # The points parameter tracks which points could have been reached by the
  82. # sequence thus far. The current sequence must be evaluated for all of those
  83. # points. Initially this is just {(0,0)} but every time a fork is encountered
  84. # the number of points will expand.
  85. proc map(sequence: Sequence, grid: var Table[Point, HashSet[Direction]], points: HashSet[Point]): HashSet[Point] =
  86. if sequence.content.len > 0:
  87. result = initSet[Point]()
  88. for point in points:
  89. var newPoint = point
  90. for d in sequence.content:
  91. newPoint = grid.addDirections(newPoint, d)
  92. result.incl(newPoint)
  93. elif sequence.forks:
  94. result = initSet[Point]()
  95. for child in sequence.nodes:
  96. result.incl(child.map(grid, points))
  97. else:
  98. result = points
  99. for child in sequence.nodes:
  100. result = child.map(grid, result)
  101. # Performs a breadth-first search of the navigable area and assigns a
  102. # distance to each point.
  103. proc score(dirs: Table[Point, HashSet[Direction]]): Table[Point, int] =
  104. result = initTable[Point, int]()
  105. var stack = initDeque[tuple[pos: Point, length: int]]()
  106. stack.addLast(((0, 0), 0))
  107. while stack.len > 0:
  108. let step = stack.popFirst
  109. if not result.hasKey(step.pos) or result[step.pos] > step.length:
  110. result[step.pos] = step.length
  111. for direction in dirs[step.pos]:
  112. stack.addLast((step.pos + directions[direction], step.length + 1))
  113. proc distances(input: string): seq[int] =
  114. var grid = initTable[Point, HashSet[Direction]]()
  115. let points = toSet([(0, 0)])
  116. discard input.process.map(grid, points)
  117. toSeq(grid.score.values)
  118. let distanceValues = input.distances
  119. echo distanceValues.max
  120. echo distanceValues.filter(proc(i: int): bool = i >= 1000).len