day6.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "os"
  7. "time"
  8. )
  9. func main() {
  10. args := flag.Bool("example", false, "example or input")
  11. flag.Parse()
  12. filename := "input.txt"
  13. if *args {
  14. filename = "example.txt"
  15. }
  16. file, err := os.Open(filename)
  17. if err != nil {
  18. fmt.Println(err)
  19. return
  20. }
  21. defer file.Close()
  22. var grid []string
  23. scanner := bufio.NewScanner(file)
  24. for scanner.Scan() {
  25. grid = append(grid, scanner.Text())
  26. }
  27. if err := scanner.Err(); err != nil {
  28. fmt.Println(err)
  29. return
  30. }
  31. startX, startY, direction := findStart(grid)
  32. // part 1
  33. start := time.Now()
  34. _, visited := simulate(grid, startX, startY, direction)
  35. lenv := make(map[[2]int]bool)
  36. for x := range visited {
  37. lenv[[2]int{x[0], x[1]}] = true
  38. }
  39. part1_time := time.Since(start)
  40. // part 2
  41. start = time.Now()
  42. loopPositions := findLoopObstacles(grid, [3]int{startX, startY, directionIndex(direction)}, visited)
  43. part2_time := time.Since(start)
  44. fmt.Println("Visited Distinct Positions:", len(lenv), "in time:", part1_time)
  45. fmt.Println("Loop Positions:", len(loopPositions), "in time:", part2_time)
  46. }
  47. func findStart(grid []string) (int, int, string) {
  48. for y, row := range grid {
  49. for x, cell := range row {
  50. switch cell {
  51. case '^':
  52. return x, y, "up"
  53. case '>':
  54. return x, y, "right"
  55. case '<':
  56. return x, y, "left"
  57. case 'v':
  58. return x, y, "down"
  59. }
  60. }
  61. }
  62. return -1, -1, ""
  63. }
  64. func simulate(grid []string, startX, startY int, direction string) (bool, map[[3]int]bool) {
  65. visited := make(map[[3]int]bool)
  66. directions := map[string][2]int{
  67. "up": {0, -1},
  68. "right": {1, 0},
  69. "down": {0, 1},
  70. "left": {-1, 0},
  71. }
  72. turnRight := map[string]string{
  73. "up": "right",
  74. "right": "down",
  75. "down": "left",
  76. "left": "up",
  77. }
  78. x, y := startX, startY
  79. visited[[3]int{x, y, directionIndex(direction)}] = true
  80. for {
  81. dx, dy := directions[direction][0], directions[direction][1]
  82. nextX, nextY := x+dx, y+dy
  83. if nextY < 0 || nextY >= len(grid) || nextX < 0 || nextX >= len(grid[nextY]) {
  84. return false, visited
  85. }
  86. if grid[nextY][nextX] == '#' {
  87. direction = turnRight[direction]
  88. continue
  89. } else {
  90. x, y = nextX, nextY
  91. }
  92. state := [3]int{x, y, directionIndex(direction)}
  93. if visited[state] {
  94. return true, visited
  95. }
  96. visited[state] = true
  97. }
  98. }
  99. func findLoopObstacles(grid []string, start [3]int, visited map[[3]int]bool) map[[2]int]bool {
  100. originalGrid := make([]string, len(grid))
  101. copy(originalGrid, grid)
  102. loopPositions := make(map[[2]int]bool)
  103. for point := range visited {
  104. x, y, _ := point[0], point[1], point[2]
  105. if x == start[0] && y == start[1] {
  106. continue
  107. }
  108. gridCopy := make([]string, len(originalGrid))
  109. copy(gridCopy, originalGrid)
  110. gridCopy[y] = replaceCharAt(gridCopy[y], x, '#')
  111. cycle, _ := simulate(gridCopy,
  112. // x-indexPairDirection(direction)[0],
  113. // y-indexPairDirection(direction)[1],
  114. // indexDirection(direction+1),
  115. start[0],
  116. start[1],
  117. indexDirection(start[2]),
  118. )
  119. if cycle {
  120. loopPositions[[2]int{x, y}] = true
  121. }
  122. }
  123. return loopPositions
  124. }
  125. func replaceCharAt(s string, index int, newChar rune) string {
  126. runes := []rune(s)
  127. runes[index] = newChar
  128. return string(runes)
  129. }
  130. func directionIndex(direction string) int {
  131. switch direction {
  132. case "up":
  133. return 0
  134. case "right":
  135. return 1
  136. case "down":
  137. return 2
  138. case "left":
  139. return 3
  140. }
  141. return -1
  142. }
  143. func indexDirection(direction int) string {
  144. switch direction % 4 {
  145. case 0:
  146. return "up"
  147. case 1:
  148. return "right"
  149. case 2:
  150. return "down"
  151. case 3:
  152. return "left"
  153. }
  154. return ""
  155. }
  156. func indexPairDirection(direction int) [2]int {
  157. switch direction {
  158. case 0:
  159. return [2]int{0, -1}
  160. case 1:
  161. return [2]int{1, 0}
  162. case 2:
  163. return [2]int{0, 1}
  164. case 3:
  165. return [2]int{-1, 0}
  166. }
  167. return [2]int{0, 0}
  168. }