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/

  • Pyro@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    9 months ago

    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)
    
      • Pyro@programming.dev
        link
        fedilink
        arrow-up
        2
        ·
        9 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.