main.pony 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. use "collections"
  2. use "files"
  3. use "format"
  4. use "itertools"
  5. // The cardinal directions
  6. primitive U
  7. primitive D
  8. primitive L
  9. primitive R
  10. type Dir is (U | D | L | R)
  11. // A basic two-dimensional point class
  12. class val Point is Hashable
  13. let _x: I64
  14. let _y: I64
  15. new val create(__x: I64, __y: I64) =>
  16. _x = __x
  17. _y = __y
  18. fun x(): I64 => _x
  19. fun y(): I64 => _y
  20. // because we want to use this as a key in a hashmap
  21. fun hash(): USize =>
  22. // hey this is probably bad, huh?
  23. _x.hash() + _y.hash()
  24. fun eq(other: Point): Bool =>
  25. (x() == other.x()) and (y() == other.y())
  26. fun ne(other: Point): Bool => not eq(other)
  27. // return the point that is `amt` squares awy from this one in `d` direction
  28. fun in_direction(d: Dir, amt: I64): Point =>
  29. match d
  30. | U => Point(_x, _y-amt)
  31. | D => Point(_x, _y+amt)
  32. | L => Point(_x-amt, _y)
  33. | R => Point(_x+amt, _y)
  34. end
  35. // print prettily
  36. fun show(): String =>
  37. "(" + Format.int[I64](_x) + ", " + Format.int[I64](_y) + ")"
  38. // An 'instruction': a direction and a distance to travel in that
  39. // direction
  40. class val Instr
  41. let _dir: Dir
  42. let _amt: I64
  43. // parse from the string repr
  44. new val from_str(s: String) ? =>
  45. _dir = match s(0)?
  46. | 'U' => U
  47. | 'D' => D
  48. | 'L' => L
  49. | 'R' => R
  50. else error
  51. end
  52. _amt = s.trim(1).i64()?
  53. // given a starting point, return the array of all the points that
  54. // would be covered by this instruction
  55. fun points(start: Point): Array[Point] =>
  56. var p = Array[Point]()
  57. for i in Range[I64](0, _amt) do
  58. p.push(start.in_direction(_dir, i+1))
  59. end
  60. p
  61. // A representation of the 'plan' for a line: nothing more than a
  62. //sequence of 'Instr's
  63. class val LinePlan
  64. let _plan: Array[Instr]
  65. new val from_str(s: String) ? =>
  66. var p = Array[Instr]()
  67. for chunk in s.split(",").values() do
  68. p.push(Instr.from_str(chunk)?)
  69. end
  70. _plan = p
  71. // this just gives the rest of the world access to our plan
  72. // mwahahahaha
  73. fun plan(): this->Array[Instr] => _plan
  74. // The value we stores in the map: a list of which wires exist at that
  75. //grid square as well as their distance along the wire
  76. class Wires
  77. let wires: Array[(U8, U64)]
  78. // we always create this with no wires initialized
  79. new create() => wires = Array[(U8, U64)]()
  80. // this adds a new wire, and if a different wire already exists at
  81. // this spot then return the combined distance from their respective
  82. // roots, otherwise return None
  83. fun ref add_wire(wire: U8, dist: U64): (U64 | None) =>
  84. wires.push((wire, dist))
  85. var min_dist: (U64 | None) = None
  86. for (w, d) in wires.values() do
  87. if wire != w then
  88. let new_dist = d + dist
  89. match min_dist
  90. | None => min_dist = new_dist
  91. | let old_min: U64 if new_dist < old_min => min_dist = new_dist
  92. end
  93. end
  94. end
  95. min_dist
  96. // for pretty-printing, return a character represents this specific
  97. //wire, using '*' for intersections
  98. fun char(): String =>
  99. var s = Set[U8]()
  100. for (w, _) in wires.values() do
  101. s.set(w)
  102. end
  103. if s.size() > 1 then "*" else
  104. try Format.int[U8](s.values().next()?) else "?" end
  105. end
  106. // the actual grid type that contains our grid logic
  107. class Grid
  108. // a map from point to wire content. we could use a 2d array or
  109. //something, but 1. this does not need to grow when we go off the
  110. //'sides' and 2. it is sparse
  111. var storage: Map[Point, Wires]
  112. // this lists the intersections, keyed by their distance
  113. var intersections: Array[(U64, Point)]
  114. new create() =>
  115. storage = Map[Point, Wires]()
  116. intersections = Array[(U64, Point)]()
  117. // print prettily
  118. fun show(env: Env) =>
  119. var min_x: I64 = 0
  120. var min_y: I64 = 0
  121. var max_x: I64 = 0
  122. var max_y: I64 = 0
  123. for p in storage.keys() do
  124. min_x = p.x().min(min_x)
  125. min_y = p.y().min(min_y)
  126. max_x = p.x().max(max_x)
  127. max_y = p.y().max(max_y)
  128. end
  129. for y in Range[I64](min_y, max_y + 1) do
  130. for x in Range[I64](min_x, max_x + 1) do
  131. if (y == 0) and (x == 0) then
  132. env.out.write("O")
  133. else
  134. try
  135. env.out.write(storage(Point(x, y))?.char())
  136. else
  137. env.out.write(".")
  138. end
  139. end
  140. end
  141. env.out.write("\n")
  142. end
  143. // set point `p` to include wire `l` at distance `dist` from its
  144. // origin. this will automatically record an intersection if point
  145. // `p` already includes a different wire
  146. fun ref set(p: Point, l: U8, dist: U64) =>
  147. storage.insert_if_absent(p, Wires.create())
  148. try
  149. let wire = storage(p)?
  150. match wire.add_wire(l, dist)
  151. | let n: U64 => intersections.push((n, p))
  152. | None => None
  153. end
  154. end
  155. // follow each `LinePlan` and adds the description of the wires to the grid
  156. fun ref addLines(plans: Array[LinePlan]) =>
  157. for (line_number, plan) in Iter[LinePlan](plans.values()).enum[U8]() do
  158. var start_point = Point(0, 0)
  159. var dist: U64 = 1
  160. for instr in plan.plan().values() do
  161. for ps in instr.points(start_point).values() do
  162. set(ps, line_number, dist)
  163. start_point = ps
  164. dist = dist + 1
  165. end
  166. end
  167. end
  168. // print all the intersection values along with the point they map to.
  169. fun show_intersections(env: Env) =>
  170. for (dist, p) in intersections.values() do
  171. env.out.print(Format.int[U64](dist) + ": " + p.show())
  172. end
  173. actor Main
  174. new create(env: Env) =>
  175. let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
  176. try
  177. with file = OpenFile(FilePath(env.root as AmbientAuth, "input.txt", caps)?) as File do
  178. var line_plans = Array[LinePlan]()
  179. for line in file.lines() do
  180. try line_plans.push(LinePlan.from_str(consume line)?) end
  181. end
  182. var grid = Grid.create()
  183. grid.addLines(line_plans)
  184. grid.show_intersections(env)
  185. end
  186. else
  187. env.err.print("Couldn't read expected file `input.txt'")
  188. env.exitcode(99)
  189. end