123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- import deques, sequtils, sets, strutils, tables
-
- type
- Point = tuple[x,y: int]
-
- Direction = enum
- N, E, S, W
-
- Sequence = ref object
- forks: bool
- nodes: seq[Sequence]
- content: seq[Direction]
-
- proc `+` (a, b: Point): Point =
- result.x = a.x + b.x
- result.y = a.y + b.y
-
- # Parses the regular expression into a 'Sequence' data structure, by pushing
- # sequences onto a stack each time a nested expression is encountered.
- #
- # Three types of sequences are found:
- # 1) "Content-only", e.g. NNNEEE
- # 2) "Forks", e.g. (subsequence|subsequence|subsequence)
- # 3) "Concatenations", e.g. NN(subsequence)EE
- #
- func process(regex: string): Sequence =
- var
- stack = initDeque[Sequence]()
- span: seq[Direction]
- result = new(Sequence)
- stack.addLast(result)
-
- for c in regex:
- if c == '^':
- continue
-
- if c in ['(', ')', '|', '$']:
- if span.len > 0:
- # If we've got bare directions, add them as a content element.
- var newSeq = new(Sequence)
- newSeq.content = span
- stack.peekLast.nodes.add(newSeq)
- span.setLen(0)
- if c == '(':
- # To start a new fork block we add two sequences: one that will
- # contain all the forked elements, and the first child to hold
- # the content of the first fork.
- var forkSeq = new(Sequence)
- forkSeq.forks = true
- stack.addLast(forkSeq)
- stack.addLast(new(Sequence))
- elif c == '|':
- # Pop the previous child off and add it to the parent fork block,
- # then add a new one for the next branch.
- var child = stack.popLast
- stack.peekLast.nodes.add(child)
- stack.addLast(new(Sequence))
- elif c == ')':
- # Pop both the child and the parent fork block off the stack.
- var child = stack.popLast
- stack.peekLast.nodes.add(child)
- var forkSeq = stack.popLast
- stack.peekLast.nodes.add(forkSeq)
- else:
- span.add(parseEnum[Direction]($c))
-
- stack.popLast
-
- let
- directions = {
- N: ( 0, 1),
- E: ( 1, 0),
- S: ( 0, -1),
- W: (-1, 0),
- }.toTable
- opposites = { N: S, E: W, S: N, W: E, }.toTable
- input = readFile("data/20.txt").strip
-
- proc addDirection(table: var Table[Point, HashSet[Direction]], point: Point, direction: Direction) =
- if not table.hasKey(point):
- table[point] = initSet[Direction]()
- table[point].incl(direction)
-
- proc addDirections(table: var Table[Point, HashSet[Direction]], point: Point, direction: Direction): Point =
- let otherPoint = point + directions[direction]
- table.addDirection(point, direction)
- table.addDirection(otherPoint, opposites[direction])
- otherPoint
-
- # Recurses through the given sequence and builds up a map of which points are
- # traversable in which directions.
- #
- # The points parameter tracks which points could have been reached by the
- # sequence thus far. The current sequence must be evaluated for all of those
- # points. Initially this is just {(0,0)} but every time a fork is encountered
- # the number of points will expand.
- proc map(sequence: Sequence, grid: var Table[Point, HashSet[Direction]], points: HashSet[Point]): HashSet[Point] =
- if sequence.content.len > 0:
- result = initSet[Point]()
- for point in points:
- var newPoint = point
- for d in sequence.content:
- newPoint = grid.addDirections(newPoint, d)
- result.incl(newPoint)
- elif sequence.forks:
- result = initSet[Point]()
- for child in sequence.nodes:
- result.incl(child.map(grid, points))
- else:
- result = points
- for child in sequence.nodes:
- result = child.map(grid, result)
-
- # Performs a breadth-first search of the navigable area and assigns a
- # distance to each point.
- proc score(dirs: Table[Point, HashSet[Direction]]): Table[Point, int] =
- result = initTable[Point, int]()
- var stack = initDeque[tuple[pos: Point, length: int]]()
- stack.addLast(((0, 0), 0))
- while stack.len > 0:
- let step = stack.popFirst
- if not result.hasKey(step.pos) or result[step.pos] > step.length:
- result[step.pos] = step.length
- for direction in dirs[step.pos]:
- stack.addLast((step.pos + directions[direction], step.length + 1))
-
- proc distances(input: string): seq[int] =
- var grid = initTable[Point, HashSet[Direction]]()
- let points = toSet([(0, 0)])
- discard input.process.map(grid, points)
- toSeq(grid.score.values)
-
- let distanceValues = input.distances
- echo distanceValues.max
- echo distanceValues.filter(proc(i: int): bool = i >= 1000).len
|