Happy New Year Everyone!
Following on from yesterday’s post, here’s a correct solution for part 2 of the puzzle for Day 1, 2025.
{--
Haskell solution for Advent Of Code 2025 day 1 (b).
Given:
* A virtual combination lock whose dial can freely rotate through
positions numbered 0 to 99 so that rotating right (clockwise) one
step from position 99 goes to position 0 and rotating left
(anticlockwise) one step from position 0 goes to position 99.
* An initial dial position of 50.
* A file `input.txt` containing a list of rotations, one per line,
of the form Lx or Rx (regex: "[LR]\d+") denoting rotations by
x steps left or right. E.g:
L68
L30
R48
L5
Find:
* The number of times the dial is either at position 0 after a rotation
or passes position 0 while being rotated.
Run with:
runghc aoc-2025-01b.hs
Expected result for supplied data (`input.txt`) is 6689.
January 1, 2026 by Jarvis Cochrane
Copyright (c) 2026 Jarvis Cochrane
--}
initial_position = 50 :: Int
-- Convert a code to a positive (right) or negative (left) integer
readCode :: String -> Int
readCode ('L':xs) = - (read xs :: Int)
readCode ('R':xs) = (read xs :: Int)
-- Convert list of codes to list of +/- integers
readCodes :: [String] -> [Int]
readCodes xs = map readCode xs
-- Rotate the dial according to the code and return a tuple with the new
-- dial position and the number of times the dial passed or stopped at 0
rotateDial :: Int -> Int -> (Int, Int)
rotateDial dialPosition code
-- If the dial has stopped on 0, count it as another zero
| newDialPosition == 0 = (newDialPosition, rotations + 1)
-- If the dial is moving from 0, only count the rotations
| dialPosition == 0 = (newDialPosition, rotations)
-- If the dial passed zero...
| linearDialPosition < 0 || linearDialPosition > 100 =
(newDialPosition, rotations + 1)
-- Otherwise, number of zeros is number of complete rotations
| otherwise = (newDialPosition, rotations)
-- Find:
-- The new absolute position of the dial, ignoring complete rotations
-- Number of complete rotations
-- The position of the dial if the value didn't wrap (i.e. on an
-- infinite linear scale). This is used to detect whether
-- newDialPosition wrapped around.
where newDialPosition = (dialPosition + code) `mod` 100
rotations = (abs code) `div` 100
linearDialPosition = dialPosition + (code `rem` 100)
-- Generate list of new positions and zeros encountered
processCodes:: Int -> [Int] -> [(Int, Int)]
processCodes dialPosition [] = []
processCodes dialPosition (x:xs) =
let (newDialPosition, zeros) = rotateDial dialPosition x
in (newDialPosition, zeros) : processCodes newDialPosition xs
-- Return the number of zeros found in the list
countZeros :: [(Int, Int)] -> Int
countZeros xs = sum [z | (_, z) <- xs]
-- Return the number of zeroes found by applying the code sequence to
-- the initial position
solve :: [String] -> Int
solve xs = countZeros $ processCodes initial_position $ readCodes xs
-- Read input file, solve, and output result
main = readFile "input.txt" >>= (putStrLn . show . solve . lines)