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/

you are viewing a single comment's thread
view the rest of the comments
[โ€“] 2 points 11 months ago (2 children)

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)
  • source
  • hideshow 2 child comments
  • [โ€“] [S] 2 points 11 months ago (1 child)

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

  • source
  • parent
  • hideshow 1 child comment
  • [โ€“] 2 points 11 months ago

    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.

  • source
  • parent