Advent of Code Day 1, 2025 (part 2)

Posted on January 1, 2026 by Jarvis Cochrane · Tagged ,

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)