summaryrefslogtreecommitdiff
path: root/03/lib.scm
blob: 6e1e4d117d92ca7986cfb514d01c2276b0a0cbcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
(library (lib)
  (export solve-part1 solve-part2)
  (import (chezscheme))

  ; Is this direction up or down?
  (define (vertical? dir)
    (= 0 (vector-ref dir 0)))

  ; Rotate stepping direction counterclockwise 90 degrees
  (define (rotate-ccw dir)
    (cond
      ((equal? dir '#(1 0))
       '#(0 1))
      ((equal? dir '#(0 1))
       '#(-1 0))
      ((equal? dir '#(-1 0))
       '#(0 -1))
      ((equal? dir '#(0 -1))
       '#(1 0))))

  ; Follow the spiral to find (x,y) `pos' of the `address'th square.
  ; The idea is to take `steps' steps in the current direction `dir',
  ; rotate `dir', take `steps' steps again, rotate, increment `steps',
  ; then start over, and so on. So the sequence starts like this:
  ;   1 step right, 1 step up,
  ;   2 steps left, 2 steps down,
  ;   3 steps right, 3 steps up,
  ;   4 steps left, 4 steps down,
  ;   etc.
  ; Note: for each value of `steps', the last stage is always up/down.
  (define (get-position address)
    (let loop ((pos '#(0 0))
               (dir '#(1 0))
               (steps 1)
               (count 0)
               (total 1))
      (if (= total address)
          pos
          (if (= count (- steps 1))
              ; Yes, this is the last step before we need to turn
              (loop
                ; Take the step, i.e. add direction to position
                (vector-map + pos dir)
                ; Rotate counterclockwise after this step
                (rotate-ccw dir)
                ; If this step is vertical, we need to increment
                (+ steps (if (vertical? dir) 1 0))
                ; Reset step counter for the current direction
                0
                ; Keep track of total steps since square one
                (+ total 1))
              ; No, this is an ordinary step, no rotation needed
              (loop
                (vector-map + pos dir)
                dir
                steps
                (+ count 1)
                (+ total 1))))))

  ; Given a position, return all eight positions adjacent to it
  (define (get-adjacent pos)
    (map
      (lambda (dir) (vector-map + pos dir))
      '(#(1 0) #(1 1) #(0 1) #(-1 1) #(-1 0) #(-1 -1) #(0 -1) #(1 -1))))

  ; Given a position, sum the values of all initialized adjacent squares
  (define (sum-adjacent pos memory)
    (fold-left
      (lambda (sum adj)
        ; Try to retrieve `pair' from memory
        (let ((pair (assoc adj memory)))
          (+ sum
             ; If position `(car pair)' has been written,
             ; add its value `(cdr pair) to the sum total.
             (if pair (cdr pair) 0))))
      0
      (get-adjacent pos)))

  (define (solve-part1 target)
    (let* ((xy (get-position target))
           (x (vector-ref xy 0))
           (y (vector-ref xy 1)))
      (+ (abs x) (abs y))))

  (define (solve-part2 target)
    (let loop ((address 2)
               (memory '((#(0 0) . 1))))
      (let ((value (cdar memory)))
        ; Has the most recently written `value' crossed the threshold?
        (if (> value target)
            value
            ; If not, move on, writing a new `val' to the next `pos'
            (let* ((pos (get-position address))
                   (val (sum-adjacent pos memory)))
              (loop
                (+ address 1)
                (cons (cons pos val) memory)))))))

)