Advent of Code 2025
Implemented the solution in Odin. Given that
the provlem invited solutions with modular arithmetic, part 2 took advantage
of the modular floor operator %%. Below is a snippet as to how it
was used in the solution:
for line in lines {
rotation, ok := strconv.parse_int( line[ 1: ] )
turns := rotation / 100
count += turns
rotation %= 100
if line[ 0 ] == 'L' {
rotation = -rotation
}
newpos := pos + rotation
if pos != 0 && newpos < 0 {
count += 1
} else
if pos != 0 && newpos > 100 {
count += 1
}
pos = newpos %% 100
if pos == 0 {
count += 1
}
} Since the %% always returns non-negative values for a positive divisor,
I didn't have to do the calculation with the operator in C the based laguages,
which would be:
pos = ( newpos + 100 ) % 100;Problem required chopping up a decimal number into even groups of digits. For part 1 this was always two groups, a high order group and a low order group. Part 2 extended this idea into any evenly divided number of digits. This forced the solution to reduce the number into different bases which are factors of 10, i.e. 10, 100, 1000, 10000, etc.
In this solution I found use for the &&= operator in Odin which is useful for accumulating
logical values and is not available in C/C++.
The solution for part 2 is below:
// Iterate through each range.
for range in ranges {
ends := strings.split( range, "-" )
start, _ := strconv.parse_i64( ends[ 0 ] )
end, _ := strconv.parse_i64( ends[ 1 ] )
// Iterate though all values in a range.
for id := start ; id <= end ; id += 1 {
digits := digit_count( id )
// Try to divide the number of digits into even groups
// by finding whole divisors of the digit count
// e.g. a number with 6 digits can be broken into
// 1-digit numbers, e.g. 111111
// 2 digit numbers, e.g. 121212
// 3-digit numbers, e.g. 123123
for factor := 1 ; factor <= digits/2 ; factor += 1 {
if ( digits % factor != 0 ) { continue }
// The base by which to chop up the number
// 1-digit -> base: 10
// 2-digit -> base: 100
base := power_10( factor )
// The lower repeated value that each group should equal
repeated_val := id % base
unit :i64= 1
all_groups_equal := true
// Loop through the groups
for i :=0 ; i < digits/factor ; i += 1 {
num :i64= ( id / unit ) % base
unit *= base
all_groups_equal &&= num == repeated_val
}
if all_groups_equal {
//fmt.printf( "Invalid id = %d\n", id )
sum += id
break
}
}
}
}
Problem was to find the maximum number (2 digit or 12 digit) from the ordered permutations of the digits of a string (i.e. bank). Brute force worked well enough for part 1, but part 2 required another approach to avoid 12 nested loops.
The chosen approach was to search from left to right starting with the most significant digit and to find the index of the left most maximum value what was right of the next higher order digit.... leaving enough room for the higher order and lower order digits. The code that I used to solve this only required 12 scans over the string, one for each digit. The essentials are below.
// Iterate through each "bank".
for bank in banks {
n_cells := len( bank )
NDIG :: 12
max_idxs : [NDIG]int
pos := 0
// Iterate through each place starting with the most significant digit
// and scan from left to right for the max value.
for d := 0 ; d < NDIG ; d += 1 {
max_idxs[ d ] = pos
for i := pos ; i < n_cells - ( NDIG - d - 1 ) ; i += 1 {
if bank[ i ] > bank[ max_idxs[ d ] ] {
max_idxs[ d ] = i
}
if bank[ max_idxs[ d ] ] == '9' {
break
}
}
pos = max_idxs[ d ] + 1
}
max_val :i64= 0
for d := 0 ; d < NDIG ; d += 1 {
digit := cast(i64) ( bank[ max_idxs[ d ] ] - '0')
max_val = 10*max_val + digit
}
sum += max_val
}
fmt.println( sum )This was a traditional game of life style problem. The second part required that I change my file read function to return a 2-D array of runes instead of an array of strings. This was necessary because I needed to keep track of which rolls were removed. The new file read funcion looks like the following:
read_file_line_by_line :: proc(filepath: string ) -> [dynamic]string {
data, ok := os.read_entire_file(filepath, context.allocator)
it := string(data)
lines : [dynamic]string
for line in strings.split_lines_iterator(&it) {
append( &lines, line )
}
return lines
}Furthermore, I started writing index based for loops in the odin way. i.e.
for r in 0 ..< R {
// body
}
for dr in -1 ..= +1 {
// body
}
}
Finally, I used the do for single line conditions after an if...
if grid[ r ][ c ] != '@' do continueThis challenge was to work with ranges of numbers. Part 1 was pretty straight forward, part 2 required reducing the ranges to mutually exclusive ranges. The heart of the algorithm in part two is as follows:
for i in 0 ..< R {
for j in i+1 ..< R {
ra := &ranges[ i ]
rb := &ranges[ j ]
if ra.ub < rb.lb do continue // Ranges don't overlap
if ra.lb == -1 do continue // Range has been eliminated
if ra.ub < rb.ub { // Back of a overlaps with front of b
rb.lb = ra.ub + 1
}
else { // Range b is entirely inside of a
rb.lb = -1
rb.ub = -1
}
}
}
count :i64= 0
for &r in ranges {
if r.lb == -1 do continue
count += r.ub - r.lb + 1
}This was the first chance I had to work with Odin's approach to collections in that the result required that I sort the ranges. It follows C's approach in that a collection and a comparison function is passed to a sort algorithm. i.e.
Range :: struct {
lb: i64,
ub: i64,
}
range_compare_lb :: proc( lhs, rhs : Range ) -> bool {
return lhs.lb < rhs.lb
}
ranges : [dynamic]Range
slice.sort_by( ranges[:], range_compare_lb )Second this is that I began working with items by reference. In my loops I obtained the range by reference rather than by copy so that I could edit them to assure the mutual exclusivity of the ranges.
This problem was relatively straight forward for both parts 1 and 2. Part 2 was a little more difficult in that it forced the construction of number across the text lines. In this problem I became a little more comfortable with dynamic arrays and slices and converting strings to numbers. The essence of part 2 is as follows:
main :: proc() {
lines := read_file_line_by_line( os.args[ 1 ] )
L := len( lines )
N := L-1
C := len( lines[0] )
numbers := lines[ :N ]
ops := lines[ N ]
sum :i64= 0
col := 0
op : u8
num_arr : [dynamic]u8
val_arr : [dynamic]i64
for i in 0 ..< C {
// get the operation
if ( col == 0 ) {
op = ops[ i ]
clear( &num_arr )
}
col += 1
for j:= 0 ; j < N ; j+=1 {
digit := cast(u8)numbers[ j ][ i ]
append( &num_arr, digit )
}
num_str := string( num_arr[:] )
num, success := strconv.parse_i64( strings.trim( num_str, " " ) )
clear( &num_arr )
if success {
append( &val_arr, num )
}
// Check to see if we have landed on a column of spaces
if ( !success || i == C-1 ) {
ans := val_arr[ 0 ]
switch op {
case '+':
for v in val_arr[ 1: ] do ans += v
case '*':
for v in val_arr[ 1: ] do ans *= v
}
sum += ans
col = 0
clear( &val_arr )
}
}
fmt.println( sum )
}