Spyke
advent_of_code·Advent Of Codebyhades

🦆 Everybody.Codes 2025 Quest 13 Solutions 🦆

Quest 13: Unlocking the Mountain

  • 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

Link to participate: https://everybody.codes/

View original on programming.dev
programming.dev

Python

A much simpler problem compared to earlier ones

# generator to yield dial values in the required order
# yields (value: str, is_reverse: bool)
def yield_dial_vals(coll: list):
    n = len(coll)
    for i in range(0, n, 2):
        yield coll[i], False
    right_start = n - 2 if n % 2 == 1 else n - 1
    for i in range(right_start, -1, -2):
        yield coll[i], True

def part1(data: str):
    nums = data.splitlines()
    n = len(nums)
    
    # total number of values on the dial
    #   + 1 for the '1' value
    total_vals = n + 1
    # effective rotation after full cycles
    effective_rot = 2025 % total_vals
    # if full cycles, return '1'
    if effective_rot == 0:
        return 1
    # adjust for 0-indexing the remaining dial values
    effective_rot -= 1
    
    # skip numbers on the dial until we reach the final value
    val_gen = yield_dial_vals(nums)
    for _ in range(effective_rot):
        next(val_gen)
    
    # return the final value
    return int(next(val_gen)[0])

assert (t := part1("""72
58
47
61
67""")) == 67, f"Expected: 67, Actual: {t}"

def part2(data: str, rotations = 20252025):
    ranges = data.splitlines()
    
    # count values on the dial other than '1'
    n = 0
    for val, _ in yield_dial_vals(ranges):
        a, b = map(int, val.split("-"))
        n += b - a + 1
    
    # total number of values on the dial
    #   + 1 for the '1' value
    total_vals = n + 1
    # effective rotation after full cycles
    effective_rot = rotations % total_vals
    # if full cycles, return '1'
    if effective_rot == 0:
        return 1
    # adjust for 0-indexing the remaining dial values
    effective_rot -= 1
    
    # iterate through ranges until we reach the one the dial lands on
    for val, is_reverse in yield_dial_vals(ranges):
        # get range bound and size
        a, b = map(int, val.split("-"))
        r = b - a + 1
        # check if the dial lands within this range
        if effective_rot < r:
            # it does!
            # return the appropriate value in the range based on direction
            if is_reverse:
                return b - effective_rot
            else:
                return a + effective_rot
        # consume the rotations for skipping this range
        effective_rot -= r
    
    assert False, "Should have found the target range"


assert part2("""10-15
12-13
20-21
19-23
30-37""") == 30

# part 3 is just part 2 with a larger number of rotations
from functools import partial
part3 = partial(part2, rotations = 202520252025)
2
hadesreply
programming.dev

I just noticed that you're probably the first person I've seen to include tests.

2

Yeah, it's an easy way to make sure everything is working correctly when I refactor or optimize. I used to keep samples in their own text files before but EC samples are small enough to include directly.

2

Rust

pub fn solve_part_1(input: &str) -> String {
    let numbers = input
        .lines()
        .map(|l| l.parse().unwrap())
        .collect::<Vec<i64>>();
    let offset = 2025 % (numbers.len() + 1);
    if offset == 0 {
        1
    } else if offset > numbers.len().div_ceil(2) {
        numbers[(numbers.len() - offset) * 2 + 1]
    } else {
        numbers[(offset - 1) * 2]
    }
    .to_string()
}

fn find_number(ranges: &[(i64, i64)], mut offset: i64, counterclockwise: bool) -> i64 {
    for (from, to) in ranges {
        let segment_size = (to - from) + 1;
        if offset >= segment_size {
            offset -= segment_size;
            continue;
        }
        return if counterclockwise {
            to - offset
        } else {
            from + offset
        };
    }
    panic!("find_number gave up and died");
}

fn solve_part_2_with_turns(input: &str, turns: i64) -> String {
    let ranges = input
        .lines()
        .map(|l| {
            let (l, r) = l.split_once("-").unwrap();
            (l.parse().unwrap(), r.parse().unwrap())
        })
        .collect::<Vec<(i64, i64)>>();
    let mut clockwise_length = 0;
    let mut clockwise_ranges = vec![];
    let mut counterclockwise_length = 0;
    let mut counterclockwise_ranges = vec![];
    for (i, (from, to)) in ranges.into_iter().enumerate() {
        if i % 2 == 0 {
            clockwise_length += to - from + 1;
            clockwise_ranges.push((from, to));
        } else {
            counterclockwise_length += to - from + 1;
            counterclockwise_ranges.push((from, to));
        }
    }
    counterclockwise_ranges.reverse();
    let offset = turns % (clockwise_length + counterclockwise_length + 1);
    if offset == 0 {
        1
    } else if offset > clockwise_length {
        find_number(
            &counterclockwise_ranges,
            offset - clockwise_length - 1,
            true,
        )
    } else {
        find_number(&clockwise_ranges, offset - 1, false)
    }
    .to_string()
}

pub fn solve_part_2(input: &str) -> String {
    solve_part_2_with_turns(input, 20252025)
}

pub fn solve_part_3(input: &str) -> String {
    solve_part_2_with_turns(input, 202520252025)
}

1

You reached the end

🦆 Everybody.Codes 2025 Quest 13 Solutions 🦆 | Spyke