On this page:
1.1 Part 1
1.2 Part 2
1.3 Complete implementation
9.0

1 Day 1: Secret Entrance🔗

We are using a rotating dial. The dial is numbered from 0 to 99. Our input is a series of dial turns. It is characterized by two things:

  • Direction: left (L) or right (R)

  • Travel length

Here is a possible input:

L68

L30

R48

It describes two rotations to the left (by 68 and 30 respectively) followed by one to the right (by 48). Going left will decrease the selected number, while going right will increase it. And since the rotary dial is circular (duh!), so going below 0 will get you up to 99, while going past 99 will get you back at 0.

The dial always starts at 50.

1.1 Part 1🔗

For the first part of the problem we are given a list of rotary moves. We need to count how many times the rotary stopped on a zero (0).

First, let’s parse our input to each move in the list becomes a simple relative number: left moves are negative and right moves are positive. That way we just need to add that number to the current position to know the next one!

(define (string->rotation s)
  (let ([dir (string-ref s 0)]
        [num (string->number (substring s 1))])
    (cond
      [(char=? dir #\L) (- num)]
      [(char=? dir #\R) num]
      [else (error "Invalid direction")])))
 
(define rotations (map string->rotation input))

Now we could stick to the problem statement and limit the values to the [0, 99] interval. However we can ignore that and notice one thing: "stopping at zero" only means stopping at a multiple of 100 (including 0), whether that multiple is positive or negative.

This trick allows us to just add the rotation to the current position, without ever worrying about modular algebra… Neato!

For this I used a "define/match" expression, alongside a recursion. With each recursive call, I "apply" the rotation a the head of the input list, until that list is depleted.

(define (part1 rotations #:start [start START-POSITION])
  (define/match (rotate rotations position zeros)
    [('() _ zeros) zeros]
    [((cons rot rest) pos zeros)
     (let ([next-pos (+ rot pos)])
       (if (zero? (remainder next-pos 100))
           (rotate rest next-pos (+ 1 zeros))
           (rotate rest next-pos zeros)))])
 
  (rotate rotations 50 0))

And that’s how it’s done!

1.2 Part 2🔗

The second part asks us to count how many time the dial passes through zero (so, for us, before a multiple of 100)!

This gets really easy for us, because the problem can be rephrase as follows:

Given a current and next positions, how many multiple of 100 does the interval between current and next contain (excluding the current position).

This is a great because there is a formulat for just that!

⌊upper/N⌋ - ⌈lower/N⌉ + 1

Again, we have to be careful to ignore current if it is a multiple of 100. Rather than doing complex interval adjustment, I rather subtracted 1 to the result when the current position was indeed a multiple of 100.

So basically, we just need to order our current and next position to have our lower and upper bounds. The results looks like this:

(define (multiples-in-range x y multiple)
 
 
  (let ([lower (min x y)]
        [upper (max x y)])
    (+ 1 (- (floor (/ upper multiple)) (ceiling (/ lower multiple))))))
 
(define (part2 rotations #:start [start START-POSITION])
   (define/match (rotate rotations position zeros)
    [('() _ zeros) zeros]
    [((cons rot rest) pos zeros)
     (let* ([next-pos (+ rot pos)]
            [nb-clicks (multiples-in-range pos next-pos 100)]
 
            [adjust (if (zero? (remainder pos 100)) -1 0)])
       (rotate rest next-pos (+ nb-clicks adjust zeros)))])
 
 
  (rotate rotations 50 0))

Quite similar as before, but thit time we count the multiple between the current ("pos") and next ("next-pos") positions.

1.3 Complete implementation🔗

#lang racket
(require racket/match)
 
(define input
  (if (file-exists? "inputs/day1.txt")
      (file->lines "inputs/day1.txt")
      (begin
       (println "Warning: 'inputs/day1.txt' is not found, using 'samples/day1.txt' instead.")
       (file->lines "samples/day1.txt"))))
 
(define (string->rotation s)
  (let ([dir (string-ref s 0)]
        [num (string->number (substring s 1))])
    (cond
      [(char=? dir #\L) (- num)]
      [(char=? dir #\R) num]
      [else (error "Invalid direction")])))
 
(define rotations (map string->rotation input))
(define START-POSITION 50)
 
; -----------------------------------------------------------------------------
; PART 1
 
(define (part1 rotations #:start [start START-POSITION])
  (define/match (rotate rotations position zeros)
    [('() _ zeros) zeros]
    [((cons rot rest) pos zeros)
     (let ([next-pos (+ rot pos)])
       (if (zero? (remainder next-pos 100))
           (rotate rest next-pos (+ 1 zeros))
           (rotate rest next-pos zeros)))
     ])
 
  (rotate rotations start 0))
 
(define solution1 (part1 rotations))
(printf "Part 1: ~a~n" solution1)
 
; -----------------------------------------------------------------------------
; PART 2
 
(define (multiples-in-range x y multiple)
  ; Number of multiples of N within interval:
  ; ⌊upper/N⌋ - ⌈lower/N⌉ + 1
  (let ([lower (min x y)]
        [upper (max x y)])
    (+ 1 (- (floor (/ upper multiple)) (ceiling (/ lower multiple))))))
 
 
 
(define (part2 rotations #:start [start START-POSITION])
   (define/match (rotate rotations position zeros)
    [('() _ zeros) zeros]
    [((cons rot rest) pos zeros)
     (let* ([next-pos (+ rot pos)]
            [nb-clicks (multiples-in-range pos next-pos 100)]
            ; ignore starting position if it's a multiple of 100
            [adjust (if (zero? (remainder pos 100)) -1 0)])
       (rotate rest next-pos (+ nb-clicks adjust zeros)))
     ])
 
  (rotate rotations 50 0))
 
(define solution2 (part2 rotations))
(printf "Part 2: ~a~n" solution2)