this post was submitted on 01 Dec 2025
47 points (100.0% liked)

Advent Of Code

1154 readers
39 users here now

An unofficial home for the advent of code community on programming.dev! Other challenges are also welcome!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

Everybody Codes is another collection of programming puzzles with seasonal events.

EC 2025

AoC 2025

Solution Threads

M T W T F S S
1 2 3 4 5 6 7
8 9 10 11 12

Rules/Guidelines

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

founded 2 years ago
MODERATORS
 

Day 1: Secret Entrance

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

top 29 comments
sorted by: hot top controversial new old
[–] eta@feddit.org 2 points 12 hours ago* (last edited 11 hours ago)

Elixir

Oh wow I'm late but that's because I decided to do this year in Elixir which I am not really experienced enough yet as it seems. Part 1 was ok but I didn't really put enough thought into Part 2 and got stuck for a while. However I got it done after a few failed tries and a few questions to AI on how to do things in Elixir.

#!/usr/bin/elixir

defmodule SafeDial do

def readInstructions(filename) do
	filename
	|> File.stream!()
	|> Stream.map(&String.trim/1)
	|> Stream.reject(&(&1 == ""))
	|> Stream.map(fn line ->
		case Regex.run(~r/^([A-Za-z])(\d+)$/, line) do
			[_, letter, number] -> {String.to_atom(letter), String.to_integer(number)}
			_ -> nil
		end
	end)
	|> Enum.reject(&(&1 == nil))
end

def evalInstructions(instructions) do
	initialPos = 50
	zeroCount = 0
	checkInstruction(instructions, initialPos, zeroCount)
end

defp checkInstruction(instructions, currPos, zeroCount) do
	case instructions do
		[] -> {currPos, zeroCount}
		[instruct | rest] ->
			nextPos =
				case instruct do
					{dir, step} when dir == :R and currPos + rem(step,100) > 99 ->
						currPos + rem(step,100) - 100
					{dir, step} when dir == :R and currPos + rem(step,100) > 99 ->
						currPos + rem(step,100) - 100
					{dir, step} when dir == :R ->
						currPos + rem(step,100)
					{dir, step} when dir == :L and currPos - rem(step,100) < 0 ->
						currPos - rem(step,100) + 100
					{dir, step} when dir == :L ->
						currPos - rem(step,100)
					_ -> raise "[ ERROR ] unkown instruction: #{inspect(instruct)}"
				end
			newZeroCount = if nextPos == 0, do: zeroCount + 1, else: zeroCount
			checkInstruction(rest, nextPos, newZeroCount)
		other -> raise "[ ERROR ] unknown instruction: #{inspect(other)}"
	end
end

def evalInstructionsPart2(instructions) do
	initialPos = 50
	zeroCount = 0
	checkInstructionPart2(instructions, initialPos, zeroCount)
end

defp checkInstructionPart2(instructions, currPos, zeroCount) do
	case instructions do
		[] -> {currPos, zeroCount}
		[instruct | rest] ->
			{nextPos, zeroCount1} =
				case instruct do
					{dir, step} when dir == :R and currPos + rem(step,100) == 100 ->
						{currPos + rem(step,100) - 100, zeroCount+floor(step/100)}
					{dir, step} when dir == :R and currPos + rem(step,100) > 99 ->
						{currPos + rem(step,100) - 100, zeroCount+floor(step/100)+1}
					{dir, step} when dir == :R ->
						{currPos + rem(step,100), zeroCount+floor(step/100)}
					{dir, step} when dir == :L and currPos == 0 and currPos - rem(step,100) < 0 ->
						{currPos - rem(step,100) + 100, zeroCount+floor(step/100)}
					{dir, step} when dir == :L and currPos - rem(step,100) < 0 ->
						{currPos - rem(step,100) + 100, zeroCount+floor(step/100)+1}
					{dir, step} when dir == :L ->
						{currPos - rem(step,100), zeroCount+floor(step/100)}
					_ -> raise "[ ERROR ] unkown instruction: #{inspect(instruct)}"
				end
			newZeroCount = if nextPos == 0, do: zeroCount1 + 1, else: zeroCount1
			checkInstructionPart2(rest, nextPos, newZeroCount)
		other -> raise "[ ERROR ] unknown instruction: #{inspect(other)}"
	end
end

end #module

IO.puts "### PART 1 ###"
instructions = SafeDial.readInstructions("input/day01Input.txt")
{finalPos, zeroCount} = SafeDial.evalInstructions(instructions)
IO.puts "final position:#{finalPos} zero count:#{zeroCount}"

IO.puts "### PART 2 ###"
instructions = SafeDial.readInstructions("input/day01Input.txt")
{finalPos, zeroCount} = SafeDial.evalInstructionsPart2(instructions)
IO.puts "final position:#{finalPos} zero count:#{zeroCount}"

Edit: removed debug output

[–] strlcpy@lemmy.sdf.org 11 points 2 days ago* (last edited 15 hours ago) (1 children)

DOS + BIOS boot (hybrid binary)

Repo | day01.asm | day01.c (prototype) | .COM download

Written in x86-16 assembly. Works as a DOS program but also as disk image for older PCs with BIOS support (or older VMs). Getting this setup to work was tricky, especially since when I started this, I had only basic x86-16 experience! Needless to say I've spent much time staring at hex numbers.

The very start of the file determines if it's running in DOS or as a bootloader, in which case it'll have to load the remainder of the file from disk and rearrange the memory layout to emulate the DOS situation.

Right now this is using just one segment of memory (64K). Input data is compressed using a custom run-length encoding scheme. The compressed background image is directly decoded into the VGA framebuffer, after which it is overwritten with the decompressed input file. Space is tight!

My main goal is to finish a few days at least with one or more game consoles supported (GameBoy Advance would be cool) and to do some cool palette tricks, like a day/night transition or animated water and stars.

[–] hades@programming.dev 2 points 2 days ago

so many years have passed since i last wrote a line of masm...

[–] janAkali@lemmy.sdf.org 3 points 2 days ago

Nim

That was the rough first day for me. Part 1 was ok. For part 2 I didn't want to go the easy route, so I was trying to find simple formulaic solution, but my answer was always off by some amount. And debugging was hard, because I I was getting the right answer for example input.
After 40 minutes I wiped everything clean and wrote a bruteforce.

Later that day I returned and solved this one properly. I had to draw many schemes and consider all the edge cases carefully to come up with code below.

type
  AOCSolution[T,U] = tuple[part1: T, part2: U]

proc solve(input: string): AOCSolution[int, int] =
  var dial = 50
  for line in input.splitLines():
    let value = parseInt(line[1..^1])
    let sign = if line[0] == 'L': -1 else: 1
    let offset = value mod 100
    result.part2 += value div 100

    if dial != 0:
      if sign < 0 and offset >= dial or
         sign > 0 and offset >= (100-dial): inc result.part2

    dial = (dial + offset * sign).euclmod(100)
    if dial == 0: inc result.part1

Full solution at Codeberg: solution.nim

[–] mykl@lemmy.world 7 points 2 days ago (1 children)

Uiua

Today's lesson: Never think that scanning the first 100 lines of input will give you a good understanding of it.

Part 2 is really messy and could probably be much simpler, but I couldn't get the logic straight in my head otherwise.

"L68 L30 R48 L5 R60 L55 L1 L99 R14 L82"
βŠœβ‹•βŠΈβ‰ @\sβˆ§βœβŠ‘β‹…@Β―βŠšβŠΈβŒ•"L"βˆ§βœβŠ‘β‹…@+βŠšβŠΈβŒ•"R"
P₁ ← ⧻⊚=0\(β—Ώ100+)βŠ‚50
P ← (
  βŠƒ(-Γ—100Γ—|/+)⌊÷100⌡⟜(⊸±) # Count and remove all over-rotations
  β‰βŠŸβ†˜Β―1⊸(\(β—Ώ100+)βŠ‚50)     # Take all positions and next moves.
  β–½βŠΈβ‰‘(β‰ 0⊒)                # Ignore any starting from zero
  +/+β†₯βŠƒ(<0|>100)≑/+       # Sum the pairs, check for passing zero.
)
Pβ‚‚ ← +βŠƒP P₁
βŠƒ(P₁|Pβ‚‚)
[–] mykl@lemmy.world 4 points 2 days ago

Just had a look at the Uiua Discord, and Part 2 can be simplified a little...

"L68 L30 R48 L5 R60 L55 L1 L99 R14 L82"
βŠœβ‹•βŠΈβ‰ @\sβˆ§βœβŠ‘β‹…@Β―βŠšβŠΈβŒ•"L"βˆ§βœβŠ‘β‹…@+βŠšβŠΈβŒ•"R"
P₁ ← ⧻⊚=0\(β—Ώ100+)βŠ‚50
Pβ‚‚ ← /+=0β—Ώ100\+β–½βŒ΅βŸœΒ±βŠ‚50
βŠƒP₁ Pβ‚‚

Sometimes I could just cry.

[–] LeixB@lemmy.world 4 points 2 days ago* (last edited 2 days ago) (1 children)

Haskell

import Control.Arrow
import Control.Monad
import Control.Monad.Writer.Strict
import Data.Char
import Data.Functor
import Text.ParserCombinators.ReadP

n = 100
start = 50

parse = fst . last . readP_to_S (endBy rotation (char '\n'))
  where
    rotation = (*) <$> ((char 'L' $> (-1)) <++ (char 'R' $> 1)) <*> (read <$> munch isDigit)

part1 = length . filter (== 0) . fmap (`mod` n) . scanl (+) start

spins :: Int -> Int -> Writer [Int] Int
spins acc x = do
    when (abs x >= n) . tell . pure $ abs x `div` n -- full loops
    let res = acc + (x `rem` n)
        res' = res `mod` n

    when (res /= res') . tell . pure $ 1

    return res'

part2 = sum . execWriter . foldM spins start

main = getContents >>= (print . (part1 &&& part2) . parse)
[–] VegOwOtenks@lemmy.world 2 points 2 days ago

I think that's a really cool usage of the Writer Monad. I thought about the State Monad but didn't end up using it. Was too hectic for me. Thanks for showing and sharing this!

[–] reboot6675@sopuli.xyz 3 points 2 days ago* (last edited 2 days ago)

Golang

func part1() {
	// file, _ := os.Open("sample.txt")
	file, _ := os.Open("input.txt")
	defer file.Close()

	scanner := bufio.NewScanner(file)
	n := 100
	current := 50
	pointingAt0 := 0

	for scanner.Scan() {
		line := scanner.Text()
		num, _ := strconv.Atoi(line[1:])
		if line[0] == 'L' {
			current = ((current-num)%n + n) % n
		} else {
			current = (current + num) % n
		}
		if current == 0 {
			pointingAt0++
		}
	}

	fmt.Println(pointingAt0)
}

func part2() {
	// file, _ := os.Open("sample.txt")
	file, _ := os.Open("input.txt")
	defer file.Close()

	scanner := bufio.NewScanner(file)
	n := 100
	current := 50
	pointingAt0 := 0

	for scanner.Scan() {
		line := scanner.Text()
		num, _ := strconv.Atoi(line[1:])

		rounds := num / n
		pointingAt0 += rounds
		num = num % n
		new := -1

		if line[0] == 'L' {
			new = ((current-num)%n + n) % n
			if current != 0 && (new > current || new == 0) {
				pointingAt0++
			}
		} else {
			new = (current + num) % n
			if current != 0 && (new < current || new == 0) {
				pointingAt0++
			}
		}

		current = new
	}

	fmt.Println(pointingAt0)
}
[–] ystael@beehaw.org 3 points 2 days ago

I agree with strlcpy -- computing was better in the 1980s. Let's try the version of the 1980s where Lisp Machines were going to power the AI future. It can't be any worse than the AI future we've got right now.

(This is SBCL, not a Lisp Machine emulator, because I'm not that hardcore.)

(defun parse-line (line)
  (let ((sign (if (eql (char line 0) #\R) 1 -1))
        (number (parse-integer (subseq line 1))))
    (* sign number)))

(defun read-inputs (filename)
  (let ((input-lines (uiop:read-file-lines filename)))
    (mapcar #'parse-line input-lines)))

(defun rotate (pos rotation)
  (mod (+ pos rotation) 100))

(defun main-1 (filename)
  (let ((rotations (read-inputs filename))
        (pos 50))
    (loop for rotation in rotations
          do (setf pos (rotate pos rotation))
          sum (if (= pos 0) 1 0))))

(defun zero-crossings (pos rotation)
  (if (> rotation 0)
      (floor (+ rotation pos) 100)
      (let ((neg-pos (if (zerop pos) pos (- pos 100))))
        (- (ceiling (+ rotation neg-pos) 100)))))

(defun main-2 (filename)
  (let ((rotations (read-inputs filename))
        (pos 50))
    (loop for rotation in rotations
          sum (zero-crossings pos rotation) into crossings
          do (setf pos (rotate pos rotation))
          finally (return crossings))))
[–] CameronDev@programming.dev 4 points 2 days ago

So, obviously the bot didnt work, thanks Ategon for covering :D

Here is the dumb solution for pt2, everyone can share in my shame:

#[test]
    fn test_2025_1_part2() {
        let input = std::fs::read_to_string("input/2025/day_1.txt").unwrap();
        let turns = input
            .lines()
            .map(|line| {
                let value = line[1..].parse::<i32>().unwrap();
                if line[0..1] == *"L" {
                    -value
                } else {
                    value
                }
            })
            .collect::<Vec<i32>>();
        let mut zero_ctr = 0;
        let mut pos = 50;
        for turn in turns {
            print!("{pos}->");

            let mut zero_passes = 0;
            if turn > 0 {
                for _ in 0..turn {
                    pos += 1;
                    if pos == 100 {
                        pos = 0;
                        zero_passes += 1;
                    }
                }
            } else {
                for _ in 0..-turn {
                    pos -= 1;
                    if pos == 0 {
                        zero_passes += 1;
                    }
                    if pos == -1 {
                        pos = 99;
                    }
                }
            };
            println!("{turn}->{pos}: {zero_passes}");
            zero_ctr += zero_passes;
        }
        println!("zero ctr: {}", zero_ctr);
    }
[–] hades@programming.dev 5 points 3 days ago (3 children)

Rust

#[derive(Default)]
pub struct Day1Solver {
    input: Vec<i64>,
}

impl Solver for Day1Solver {
    fn presolve(&mut self, input: &str) {
        self.input = input
            .trim()
            .split("\n")
            .map(|line| {
                if let Some(n) = line.strip_prefix('L') {
                    -n.parse::<i64>().unwrap()
                } else if let Some(n) = line.strip_prefix('R') {
                    n.parse().unwrap()
                } else {
                    panic!("what: {line}");
                }
            })
            .collect();
    }

    fn solve_part_one(&mut self) -> String {
        let mut p = 50;
        let mut count = 0;
        for n in self.input.clone() {
            p += n;
            if p % 100 == 0 {
                count += 1;
            }
        }
        count.to_string()
    }

    fn solve_part_two(&mut self) -> String {
        let mut count = 0;
        let mut p = 1000000000050;
        for i in self.input.clone() {
            if p % 100 == 0 {
                count += (i / 100).abs();
            } else {
                count += ((p + i) / 100 - p / 100).abs();
                if i < 0 && (p + i) % 100 == 0 {
                    count += 1;
                }
            }
            p += i;
        }
        count.to_string()
    }
}
[–] Gobbel2000@programming.dev 4 points 2 days ago (1 children)

Nice solution. Just a little Rust tip if you don't mind: In this case you can avoid cloning the input Vec in the loops by instead looping over references into the list with for n in self.input.iter() or simpler for n in &self.input. The only difference is that n will be of type &i64 instead of i64.

[–] hades@programming.dev 3 points 2 days ago

Thanks! I don’t mind tips at all.

[–] Deebster@programming.dev 2 points 2 days ago (1 children)

I'm far from sure I understand what you're doing in part 2, but I think maybe you hit the same logic bug I did (I solved it with a shameful if statement that I will have to fix later).

[–] hades@programming.dev 3 points 2 days ago (1 children)

the idea is that crossing the zero is easy to detect by dividing the total dial movement by 100. I.e. if you cross from 120 to 90 you will detect that 120/100=1 changed to 90/100=0. The only case when this doesn’t work is when you stop at zero going in the negative direction, hence the extra if

[–] Deebster@programming.dev 1 points 2 days ago (1 children)

The thing I couldn't comprehend was where 1000000000050 had come from.

[–] hades@programming.dev 2 points 2 days ago

ah that’s just because i needed rounding towards infinity and not towards zero. In other words i wanted -10/100 to be -1 and not zero. But i couldn’t figure it out on the spot, so i just made it never negative :)

[–] VegOwOtenks@lemmy.world 1 points 2 days ago (1 children)

How could I run this code? Do you use some kind of framework for AoC?

[–] hades@programming.dev 1 points 2 days ago

you could say that, yeah, it downloads the inputs, submits the answers, and keeps track of wrong answers. You can grab my entire repository here: https://github.com/hades/aoc25/tree/master

[–] Gobbel2000@programming.dev 3 points 2 days ago

Rust

Almost missed, that "the dial starts by pointing at 50".

View on github

const N: i32 = 100;

fn parse_line(l: &str) -> (i32, i32) {
    let dir = match l.chars().next().unwrap() {
        'L' => -1,
        'R' => 1,
        _ => panic!(),
    };
    let dist = l[1..].parse::<i32>().unwrap();
    (dir, dist)
}

fn part1(input: String) {
    let mut pos = 50;
    let mut count0 = 0;
    for l in input.lines() {
        let (dir, dist) = parse_line(l);
        pos = (pos + dir * dist) % N;
        if pos == 0 {
            count0 += 1;
        }
    }
    println!("{count0}");
}

fn part2(input: String) {
    let mut pos = 50;
    let mut count0 = 0;
    for l in input.lines() {
        let (dir, dist) = parse_line(l);
        if dir == 1 {
            count0 += (pos + dist) / N;
        } else {
            count0 += ((N - pos) % N + dist) / N;
        }
        pos = (pos + dir * dist).rem_euclid(N);
    }
    println!("{count0}");
}

util::aoc_main!();
[–] h4x0r@lemmy.dbzer0.com 2 points 2 days ago* (last edited 2 days ago)

c

#include "aoc.h"
#include <stdio.h>
#include <string.h>

constexpr usize LINE_BUFSZ = (1 << 3);
constexpr i32 START = 50;
constexpr i32 TOP = 100;

static void
solve(Mode mode) {
  FILE* input = fopen("input", "r");
  c8 line[LINE_BUFSZ] = {};
  u32 zs = 0;
  i32 idx = START;
  while (fgets(line, sizeof(line), input)) {
    line[strcspn(line, "\n")] = 0;
    i32 val = 0;
    sscanf(&line[1], "%d", &val);
    if (mode == MODE_ONE) {
      i32 d = line[0] == 'L' ? -val : val;
      idx = (idx + d) % TOP;
      idx += idx < 0 ? TOP : 0;
      idx == 0 ? zs++ : 0;
    } else {
      for (i32 i = 0; i < val; i++) {
        idx = line[0] == 'L' ? idx - 1 : idx + 1;
        idx = idx == -1 ? TOP - 1 : idx;
        idx = idx == TOP ? 0 : idx;
        idx == 0 ? zs++ : 0;
      }
    }
  }
  fclose(input);
  printf("%u\n", zs);
}

i32
main(void) {
  solve(MODE_ONE);
  solve(MODE_TWO);
}
[–] Zikeji@programming.dev 4 points 3 days ago (1 children)

A straightforward day 1 for a Javascript / NodeJS solution.

Poorly Optimized JS Solution

let position = 50;
let answer1 = 0;
let answer2 = 0;

const sequence = require('fs').readFileSync('input-day1.txt', 'utf-8');

sequence.split('\n').forEach(instruction => {
    const turn = instruction.charAt(0);
    let distance = parseInt(instruction.slice(1), 10);

    while (distance > 0) {
        distance -= 1;

        if (turn === 'L') {
            position -= 1;
        } else if (turn === 'R') {
            position += 1;
        }

        if (position >= 100) {
            position -= 100;
        } else if (position < 0) {
            position += 100;
        }

        if (position === 0) {
            answer2 += 1;
        }
    }

    if (position === 0) {
        answer1 += 1;
    }
});

console.log(`Part 1 Answer: ${answer1}`);
console.log(`Part 2 Answer: ${answer2}`);

[–] Deebster@programming.dev 2 points 2 days ago

Brute forcing it like this was my first thought - like they say: if it's stupid and it works, it's not stupid.

[–] tranzystorek_io@beehaw.org 2 points 3 days ago* (last edited 2 days ago)
[–] VegOwOtenks@lemmy.world 2 points 3 days ago

The struggled with a counting solution for a long time. I submitted with a simple enumerative solution in the end but managed to get it right after some pause time:

Haskell

Fast to Run, Stepwise Solution

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OrPatterns #-}
module Main (main) where

import Control.Monad ( (<$!>) )
import qualified Data.List as List

main :: IO ()
main = do
  rotations <- (fmap parseRotation . init . lines) <$!> getContents
  print $ part1 rotations
  print $ part2 rotations

part2 :: [Either Int Int] -> Int
part2 rotations = let

    foldRotation (position, zeroCount) operation = case operation of
      Left y -> let
        (zeroPasses, y') = y `divMod` 100
        position' = (position - y') `mod` 100
        zeroCount' = zeroPasses + zeroCount + if position <= y' then fromEnum $ position /= 0 else 0
        in (position', zeroCount')
      Right y -> let
        (zeroPasses, y') = y `divMod` 100
        position' = (position + y') `mod` 100
        zeroCount' = zeroPasses + zeroCount + if y' + position >= 100 then 1 else 0
        in (position', zeroCount')

  in snd $ List.foldl' foldRotation (50, 0) rotations

part1 :: [Either Int Int] -> Int
part1 rotations = let
    positions = List.scanl applyRotation 50 rotations
  in List.length . filter (== 0) $ positions

applyRotation :: Int -> Either Int Int -> Int
applyRotation x = \case
  Left y -> (x - y) `mod` 100
  Right y -> (x + y) `mod` 100

parseRotation :: String -> Either Int Int
parseRotation = \case
  'R':rest -> Right $ read rest
  'L':rest -> Left $ read rest
  bad -> error $ "invalid rotation operation: " ++ bad

Fast to Code, Exhaustively Enumerating Solution

-- | Old solution enumerating all the numbers

part2' :: [Either Int Int] -> Int
part2' rotations = let
  intermediatePositions _ [] = []
  intermediatePositions x (op:ops) = case op of
    Left 0; Right 0 -> intermediatePositions x ops
    Left y -> let x' = pred x `mod` 100 in x' : intermediatePositions x' (Left (pred y) : ops)
    Right y -> let x' = succ x `mod` 100 in x' : intermediatePositions x' (Right (pred y) : ops)
  in List.length . List.filter (== 0) . intermediatePositions 50 $ rotations

[–] Deebster@programming.dev 1 points 2 days ago* (last edited 2 days ago)

Rust

use std::{cmp::Ordering, fs, str::FromStr};

use color_eyre::eyre::{Result, bail};

const DIAL_NUMBERS: isize = 100;

#[derive(Clone, Copy)]
struct Dial(usize);

impl Dial {
    fn new() -> Self {
        Self(50)
    }

    fn rotate(&mut self, rot: isize) -> usize {
        let pass_0s;

        let total = rot.wrapping_add_unsigned(self.0);
        match total.cmp(&0) {
            Ordering::Equal => {
                pass_0s = 1;
                self.0 = 0;
            }
            Ordering::Less => {
                // Starting on 0 means we don't cross it
                let started_0 = if self.0 == 0 { 1 } else { 0 };
                pass_0s = 1 + (-total / DIAL_NUMBERS) as usize - started_0;
                self.0 = (self.0 as isize + rot).rem_euclid(DIAL_NUMBERS) as usize;
            }
            Ordering::Greater => {
                let full_turns = total / DIAL_NUMBERS;
                pass_0s = full_turns as usize;
                self.0 = (total - DIAL_NUMBERS * full_turns) as usize;
            }
        };

        pass_0s
    }

    fn sequence(&mut self, s: &str) -> Result<(usize, usize)> {
        let mut end_0s = 0;
        let mut pass_0s = 0;

        for l in s.lines() {
            let num = isize::from_str(&l[1..]).unwrap();
            let dir = l.bytes().next().unwrap();
            pass_0s += match dir {
                b'L' => self.rotate(-num),
                b'R' => self.rotate(num),
                _ => bail!("b{dir} is an invalid rotation direction"),
            };
            if self.0 == 0 {
                end_0s += 1;
            }
        }

        Ok((end_0s, pass_0s))
    }
}

fn parts(filepath: &str) -> Result<(usize, usize)> {
    let input = fs::read_to_string(filepath)?;
    let mut dial = Dial::new();
    let res = dial.sequence(&input)?;
    Ok(res)
}

fn main() -> Result<()> {
    color_eyre::install()?;

    let (p1, p2) = parts("d01/input.txt")?;
    println!("Part 1: {p1}");
    println!("Part 2: {p2}");
    Ok(())
}

I lost a lot of time struggling with edge cases in part two since I thought it was wasteful to run rem_euclid() (i.e. modulus) when I'd already got the information to do it more efficiently. While I got there in the end my final code was a bit ugly. This prettier, "unoptimised" version runs in 1.6ms, so I was being an idiot.

[–] eco_game@discuss.tchncs.de 1 points 2 days ago (1 children)
[–] chunkystyles@sopuli.xyz 1 points 2 days ago* (last edited 2 days ago)

Here's my Kotlin version for Part 2.

fun main() {
    var currentDialPosition = 50
    var password = 0
    val input = getInput(1)
    val turns = parseInput2(input)
    turns.forEach {
        if (it.second > 0) {
            password += it.second
        }
        var newDialPosition = currentDialPosition + it.first
        if (newDialPosition < 0) {
            newDialPosition += 100
            if (currentDialPosition != 0) {
                password++
            }
        } else if (newDialPosition > 99) {
            newDialPosition %= 100
            password++
        } else if (newDialPosition == 0) {
            password++
        }
        currentDialPosition = newDialPosition
    }
    println(password)
}

fun parseInput2(input: String): List<Pair<Int, Int>> = input
    .split("\n")
    .filter { it.isNotBlank() }
    .map {
        val direction = it.first()
        val number = it.slice(1..<it.length).toInt()
        val clicks = number % 100
        val fullTurns = number / 100
        if (direction == 'L') {
            clicks * -1 to fullTurns
        } else {
            clicks to fullTurns
        }
    }