Seth Barrett

Daily Blog Post: September 18th, 2025

Julia Icon

September 18th, 2025

Julia Practice: Advent of Code 2024 - Day 2

Day 2.0

I enjoyed the challenge of day 1, so today I'm using my copious amounts of free time to work on day 2 of Advent of Code 2024. Today's assignment contains "reports" (lines of the input file), each containing lists of 5 integers separated by spaces, representing "levels". The goal of this assignment is to figure out the number of reports that are "safe"; reports are safe if 2 things are true:

  1. The levels are either all increasing or all decreasing.
  2. Any two adjacent levels differ by at least one and at most three.
This lead me to start out by making an isSafe function, where the argument passed in is a Vector of distances between levels in a report. I have a sanity check to make sure the line isn't empty, returning false early if so. I then check to see if the differences are either all positive or all negative, returning false early if this is not the case. Lastly, I return the boolean value representing if the absolute value of all the diffs is between 1 & 3. Code is below:

function isSafe(distances::AbstractVector{<:Integer})::Bool
    isempty(diffs) && return false
    req1 = all(>(0), diffs) || all(<(0), diffs)
    req1 || return false
    return all(1 .<= abs.(diffs) .<= 3)
end

After I got this working, I combined it with the basic boiler plate for activating the package, readlines for reading the input file, and a basic int parsing script for creating a Vector of ints for each level, then created a Vector of ints to represent the distances of each level to its neighbor. This, in a for loop, allowed me to calculate the number of safe levels:

using Pkg
Pkg.activate(".")

lines = readlines("input/day2.txt")
function main()::Int64
    safeReports = 0
    for line in lines
        nums = [parse(Int64, strNum) for strNum in split(line)]
        distances = [nums[i] - nums[i + 1] for i in 1:length(nums) - 1]

        if isSafe(distances)
            safeReports += 1
        end
    end
    return safeReports
end

safe1, safe2 = main()
println("Day2.0: $safe1\nDay2.5: $safe2")

Day 2.5

One of the cool things with the advent of code problems is that occasionally, the second half of the problem makes you completely rethink how you solved the first half of the problem. That's what happened today.

The instructions for this half of the day include a so called "Problem Dampener", which allows the tolerance of one bad level. What this means in practice is that the same rules from earlier apply, but we also can declare a report safe if removal of one level. This means that my old strategy of using all won't fly anymore, which lead me to doing some more research on the Julia language.

First thing I found were two changes I could make to help the verbosity of my main function. Firstly, I found that there was diff() built-in function in Julia that was exactly what I was looking for. I replaced distances = [nums[i] - nums[i + 1] for i in 1:length(nums) - 1] with diff(nums) (you can kinda tell I use Python all day from my first approach)/ Secondly, I turned my line parsing code from nums = [parse(Int64, strNum) for strNum in split(line)] into a one-line function parse_levels(line::AbstractString)::Vector{Int} = parse.(Int, split(line)) (pretty cool feature Julia imo, reminds me of lambda but cooler).

After getting those small tweaks out of the way, I got started on a separate function for checking if is_safe_with_dampener, that would only activate when isSafe returns false for a report. My approach to this was to iterate through the indices of a report, and for each index, create a new Vector of integers with all the levels besides the one at the index. Then I check to see if this new Vector of level safe using is_safe(diff(cand)) && return true, returning true if this new Vector is safe. If none of the newly created Vectors are safe, I return false. The code for this, plus my new main method is below:

function is_safe_with_dampener(levels::AbstractVector{<:Integer})::Bool
    n = length(levels)

    for i in 1:n
        cand = Vector{eltype(levels)}(undef, n - 1)
        if i > 1
            cand[1:i-1] = @view levels[1:i-1]
        end
        if i < n
            cand[i:n-1] = @view levels[i+1:n]
        end
        is_safe(diff(cand)) && return true
    end
    return false
end

function main(path::AbstractString)::Tuple{Int,Int}
    part1::Int = 0
    part2::Int = 0
    for line in eachline(path)
        levels = parse_levels(line)
        if is_safe(diff(levels))
            part1 += 1
            part2 += 1     
        elseif is_safe_with_dampener(levels)
            part2 += 1
        end
    end
    return (part1, part2)
end

Conclusions

Even with how busy PhD, TA & Kartchaser is keeping me, I'm having a blast working on these, and plan on continuing this grind. My code can be found on my GitHub in this repo.