summaryrefslogtreecommitdiff
path: root/10/lib.scm
blob: 11ad44908b6d9b5a77b1ab66bd8b8988b58a930c (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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
(library (lib)
  (export solve-part1 solve-part2)
  (import (chezscheme))

  ; Split list at first delimiter into `prefix' and `suffix'
  ; Return value is a pair like `((p r e f i x) s u f f i x)'
  (define (list-split-left delimiter? xs)
    (let loop ((prefix '())
               (suffix xs))
      (if (null? suffix)
        (cons prefix suffix)
        (let ((x (car suffix)))
          (if (delimiter? x)
              ; Found first delimiter, so return immediately
              (cons prefix (cdr suffix))
              ; `x' isn't a delimiter, so append it to `prefix'
              (loop (append prefix (list x)) (cdr suffix)))))))

  ; Split list at given delimiter into list of sublists
  (define (list-split delimiter? xs)
    (let loop ((pieces '())
               (rest xs))
      (if (null? rest)
          ; Fix order and remove all empty sublists from output
          ; (which are caused by consecutive delimiters in `xs')
          (reverse (remp null? pieces))
          ; Extract next piece from `rest' and prepend it to `pieces'
          (let ((next (list-split-left delimiter? rest)))
            (loop (cons (car next) pieces) (cdr next))))))

  ; Split string at commas into list of substrings
  (define (string-split str)
    (define (char-comma? c) (char=? c #\,))
    (map list->string (list-split char-comma? (string->list str))))

  ; Reverse order of numbers in index range `[pos, pos+len)' in state
  (define (reverse-range state-old pos len)
    (define state-length (vector-length state-old))
    ; Initialize `state-new' as a copy of `state-old'
    (define state-new (apply vector (vector->list state-old)))
    (let loop ((i 0)) ; `i' is the offset from `pos'
      (if (>= i len)
          state-new
          (begin
            ; Copy `state-old[pos+i]' to `state-new[pos+len-i-1]'
            (vector-set!
              state-new
              (mod (+ pos i) state-length)
              (vector-ref
                state-old
                (mod (- (+ pos len) i 1) state-length)))
            (loop (+ i 1))))))

  ; Apply a single round of the (main phase of the) hash function
  (define (single-round lengths0 state0 pos0 skip0)
    (let loop ((lengths lengths0)
               (state   state0)
               (pos     pos0)
               (skip    skip0))
      (if (null? lengths)
          ; Once we've exhausted `lengths', return the full internal state
          (values state pos skip)
          ; Call `reverse-range' for `(car lengths)', and update state
          (loop
            (cdr lengths)
            (reverse-range state pos (car lengths))
            ; (We just let `pos' grow, ignoring circularity)
            (+ pos (car lengths) skip)
            (+ skip 1)))))

  ; Apply `single-round' `n' times, reusing the full internal state each time
  (define (multi-round n lengths state-old pos-old skip-old)
    (if (<= n 0)
        state-old
        (let-values
          (((state-new pos-new skip-new)
            (single-round lengths state-old pos-old skip-old)))
          (multi-round (- n 1) lengths state-new pos-new skip-new))))

  ; Initialize state and apply `n' rounds of knot-tying algorithm
  (define (solve-puzzle n lengths)
    (define state0 (list->vector (iota 256)))
    (multi-round n lengths state0 0 0))

  ; Convert "sparse hash" to "dense hash" as per part 2's description
  (define (sparse->dense sparse)
    ; At first, storing `state' as a vector was more convenient
    ; because of all the indexing, but now a list seems better.
    (let loop ((state (vector->list sparse))
               (dense '()))
      (if (null? state)
          (reverse dense)
          (loop
            ; Remove the first 16 elements from `sparse'
            (cddddr (cddddr (cddddr (cddddr state))))
            (cons
              ; XOR together the first 16 elements of `sparse'
              (apply bitwise-xor
                     (list-tail (reverse state) (- (length state) 16)))
              dense)))))

  (define (solve-part1 str)
    (define lengths (map string->number (string-split str)))
    (define state (solve-puzzle 1 lengths))
    (* (vector-ref state 0) (vector-ref state 1)))

  (define (solve-part2 str)
    ; For part 2, `lengths' must be initialized differently
    (define lengths
      (append (map char->integer (string->list str))
              '(17 31 73 47 23)))
    (define sparse-hash (solve-puzzle 64 lengths))
    (define dense-hash (sparse->dense sparse-hash))
    ; Convert `dense-hash' into lower-case hexadecimal representation
    (let loop ((bytes dense-hash)
               (final ""))
      (if (null? bytes)
          final
          (loop
            (cdr bytes)
            (string-append final (format #f "~(~2,'0x~)" (car bytes)))))))

)