Advent of Code 2025 - Day 1

professional
Exploring and solving Day 1 of the 2025 Advent of Code using R.
Author

Griffin Judy

Published

December 1, 2025

Intro

Hello everyone! This year, I will be attempting to complete the Advent of Code (AOC) and posting the code on my website. For those unfamiliar, the Advent of Code is a fun series of coding puzzles in the style of an advent calendar. The puzzles vary in difficulty throughout the month and are language-agnostic. Daily completions will be posted in the Blog, and the completed compilation will be posted in Projects. I am a data analyst (if you couldn’t tell by the R), not a “true” programmer, so there will be room for improvement! Practice and improvement are the goal.

The AOC posts will be long, with an emphasis on explanation and understanding. They are not a tutorial, and are more of a glimpse into how I work and think through coding problems.

Part 1

Prompt

Part 1 has the elves rotating a dial on a safe to receive a password. The dial has 0-99 on it. The direction of the rotation is denoted by an “L” or “R” present before the number of rotations. For example, “R11” would be “turn to the right 11” and “L15” would be “turn to the left 15”. There are no negative numbers on a dial, so moving 1 to the left (L1) at 0 would rollover to 99. Likewise, moving one to the right (R1) at 99 would rollover to 0. The dial starts on 50. The final answer will be the number of times that the dial lands on exactly 0 after rotating.

Planning

There are 3 challenges to address before attempting to solve the problem:

  • Read in the text file []
  • Separate the Left (L) and Right (R) designation from the numbers []
  • Account for rollover []

Executing

Challenge 1

This should all be doable using base R. Step 1 is reading in the text file. The text file has each rotation as its own line. It looks like:

Example AOC Data
R1
L55
R17
R13
L15

After some Googling, I learned about the readLines function in base R. It reads in a text file line-by-line and assigns the data into a vector. It seems to work perfectly!

#Using PATH instead of the actual path for privacy. 
#If following along, use the location of the data here e.g. //local//data.txt
ROTATIONS <- readLines(PATH)
#head gives a glimpse of the first 6 
head(ROTATIONS)
[1] "R1"  "R25" "R50" "R10" "L20" "L14"
#length tells us the size of the vector
length(ROTATIONS)
[1] 4577

Challenge 1 addressed!

  • Read in the text file [X]

Challenge 2

Next, rotation direction needs to be separated from rotation quantity. This can be done in a few ways, but practicing base R is always useful. The substr function separates the direction and the amount.

#this is done in base R but it is probably easier to use the stringr package
#earlier I wrote that I thought it was doable in base R so we use substr
dir <- substr(ROTATIONS[5], 1, 1)
dir
[1] "L"
#using as.numeric to ensure it is a double instead of a char 
amt <- as.numeric(substr(ROTATIONS[5], 2, nchar(ROTATIONS[5])))
amt
[1] 20
#check type if desired
#typeof(amt)

#lefts need to be treated as negative since L20 is the same as subtracting 20
if(dir == "L"){
    amt <- (amt * -1)        
}
#it's negative
amt
[1] -20

Challenge 2 is addressed!

  • Separate the Left (L) and Right (R) designation from the numbers [X]

Challenge 3

Accounting for the rollover is a simple but important step. If the mathematical result of the rotation (not the rotation itself!) is negative, the negative number should be added to multiples of 100 until it is positive. For example, L1 at 0 would result in -1, but the dial is round so it rolls over to 99. 100 + -1 = 99. This pattern continues; starting at 10, L220 results in -210. 300 + -210 = 90.

while(amt < 0){
    amt <- amt + 100
}

The right rollover is easier. 99 R1 is 100, but it needs to be 0, so if the amount is greater than 99, 100 should be subtracted from it. For example, starting at 90, R20 results in 110. 110 - 100 = 10. This pattern continues.

As a quick note, this is not the most efficient way to do this, but it works. You could reduce the number of function calls by getting the amt / 100, then multiplying the rounded quotient by 100 and subtracting that from amt. The more efficient way looks messier, and I want to practice while loops in R, so it is done this way! It took a few failed submission attempts to realize that the rotation amount itself is not limited to 1-99, leading to the dial having a final value of >99 or <0 after only adding 100 or -100. Transforming what was previously an if statement into a while statement fixed it!

while(amt > 99){
    amt <- amt - 100
}

Challenge 3 is addressed!

  • Read in the text file [X]
  • Separate the Left (L) and Right (R) designation from the numbers [X]
  • Account for rollover [X]

Combining

Now we can combine everything for the final code! It is important to remember that the prompt wants us to count the number of times the dial lands on exactly 0.

#what the mathematical outcome of the dial would be
dial <- 50

#initiate the count of zeros (what we are looking for) at 0
zeros <- 0

for(i in 1:length(ROTATIONS)){
    
    #separate everything
    dir <- substr(ROTATIONS[i], 1, 1)
    amt <- as.numeric(substr(ROTATIONS[i], 2, nchar(ROTATIONS[i])))

    #make left turns negative
    if(dir == "L"){
        amt <- (amt * -1)        
    }

    #mathematically move the dial
    dial <- (dial + amt)
    
    #left rollover
    while(dial < 0){
        dial <- (dial + 100)
    }

    #right rollover
    while(dial > 99){
        dial <- (dial - 100)
    }
    
    #count the 0s
    if (dial == 0){
        zeros <- (zeros + 1)        
    }
    #print(dial)
}
zeros
[1] 1139

Part 2

Prompt

Part 2 has us count the number of times the dial passes or lands on zero. This took me a few hours and a lot of debugging to solve. I also had to look at hints on the Advent of Code subreddit. Adding zeros <- (zeros + 1) to the while loops causes zero to be counted twice when landing on it. There is probably a more elegant solution to this problem, but I wanted to keep as much code as possible.

#what the mathematical outcome of the dial would be
dial <- 50

#initiate the count of zeros (what we are looking for) at 0
zeros <- 0

#the starting dial for check when starting at 0
start_dial <- 0

for(i in 1:length(ROTATIONS)){
    
    #separate everything
    dir <- substr(ROTATIONS[i], 1, 1)
    amt <- as.numeric(substr(ROTATIONS[i], 2, nchar(ROTATIONS[i])))
    
    # Store the starting position
    start_dial <- dial
    
    # make left turns negative
    if(dir == "L"){
        amt <- (amt * -1)        
    }
    
    # mathematically move the dial
    dial <- (dial + amt)
    
    # Track if 0 was carried
    carry <- FALSE
    
    if(dial < 0){
        # Special handling when starting from 0
        if(start_dial == 0){
            #floor rounds down, opposite of ceiling
            zeros <- zeros + floor(abs(dial) / 100)
        } else {
            zeros <- zeros + floor(abs(dial) / 100) + 1
        }
        carry <- TRUE
    } else if(dial > 99){
        zeros <- zeros + floor(dial / 100)
        carry <- TRUE
    }
    
    #left rollover
    while(dial < 0){
        dial <- (dial + 100)
    }#if

    #right rollover
    while(dial > 99){
        dial <- (dial - 100)
    }#if
    
    # Only count landing on 0 if we didn't carry
    if(dial == 0 && carry == FALSE){
        zeros <- zeros + 1
    }
}

zeros
[1] 6684

Conclusion

I had fun completing Part 1. The prompt helped me visualize and understand what was occurring. Part 2 was significantly more difficult, and I needed help to finish the problem. In the future, I should build more test cases to expedite finding issues. Thank you for reading!