lily's recent activity

  1. Comment on Day 25: Snowverload in ~comp.advent_of_code

    lily
    Link
    I should've been faster with this one. I haven't done a great job at finishing the last few problems, but today's wasn't too bad thanks to NetworkX doing most of the work for me. I might go back...

    I should've been faster with this one. I haven't done a great job at finishing the last few problems, but today's wasn't too bad thanks to NetworkX doing most of the work for me. I might go back to this at some point and try to implement the algorithm myself, but I don't have time for that right now.

    Solution
    # Advent of Code 2023
    # Day 25: Snowverload
    
    import networkx
    
    graph = {}
    
    with open("inputs/day_25.txt") as file:
        for line in file:
            component = line[:3]
    
            if component in graph:
                graph[component].update(set(line[5:].rstrip("\n").split(" ")))
            else:
                graph[component] = set(line[5:].rstrip("\n").split(" "))
    
            for other in graph[component]:
                if other not in graph:
                    graph[other] = set([component])
                else:
                    graph[other].add(component)
    
    to_remove = tuple(networkx.minimum_edge_cut(networkx.from_dict_of_lists(graph)))
    
    for edge in to_remove:
        graph[edge[0]].remove(edge[1])
        graph[edge[1]].remove(edge[0])
    
    product = 1
    
    for edge in to_remove[0]:
        queue = [edge]
        seen = set()
    
        while queue:
            current = queue.pop(0)
    
            for next_ in graph[current]:
                if next_ not in seen:
                    queue.append(next_)
                    seen.add(next_)
    
        product *= len(seen)
    
    print(f"Part 1: {product}")
    
    2 votes
  2. Comment on Day 20: Pulse Propagation in ~comp.advent_of_code

    lily
    (edited )
    Link
    Tricky part 2 today. I honestly wasn't the hugest fan of today's problem, since the solution ended up being dependent on a quirk in the input, and so it took me longer to figure out than it...

    Tricky part 2 today. I honestly wasn't the hugest fan of today's problem, since the solution ended up being dependent on a quirk in the input, and so it took me longer to figure out than it probably should have.

    Solution
    # Advent of Code 2023
    # Day 20: Pulse Propagation
    
    from math import lcm
    
    class FlipFlop:
        def __init__(self, destinations):
            self.destinations = destinations
            self.on = False
    
        def recieve(self, name, pulse):
            if pulse == "low":
                self.on = not self.on
    
                if self.on:
                    return [(name, "high") for name in self.destinations]
                else:
                    return [(name, "low") for name in self.destinations]
            else:
                return []
    
    class Conjunction:
        def __init__(self, destinations):
            self.destinations = destinations
        
        def init_memory(self, connected_modules):
            self.memory = {name: "low" for name in connected_modules}
    
        def recieve(self, name, pulse):
            self.memory[name] = pulse
    
            if all(pulse == "high" for pulse in self.memory.values()):
                return [(name, "low") for name in self.destinations]
            else:
                return [(name, "high") for name in self.destinations]
    
    modules = {}
    conjunctions = {}
    
    with open("inputs/day_20.txt") as file:
        for line in file:
            if line.startswith("broadcaster"):
                starting_destinations = (
                    line.split(" -> ")[1].rstrip("\n").split(", ")
                )
            else:
                halves = line.split(" -> ")
                kind = (FlipFlop, Conjunction)[halves[0][0] == "&"]
                name = halves[0][1:]
                destinations = halves[1].rstrip("\n").split(", ")
    
                modules[name] = kind(destinations)
    
                if kind == Conjunction:
                    conjunctions[name] = modules[name]
    
                if "rx" in destinations:
                    final_conjunction = modules[name]
    
    for conjunction_name, conjunction in conjunctions.items():
        connected_modules = []
    
        if conjunction_name in starting_destinations:
            connected_modules.append("broadcaster")
    
        for name, module in modules.items():
            if conjunction_name in module.destinations:
                connected_modules.append(name)
    
        conjunction.init_memory(connected_modules)
    
    low_pulses = 1000
    high_pulses = 0
    
    presses = 1
    cycles = {name:None for name in final_conjunction.memory}
    
    while presses <= 1000 or any(cycle is None for cycle in cycles.values()):
        pulses = [("broadcaster", name, "low") for name in starting_destinations]
    
        while pulses:
            next_pulses = []
    
            for pulse in pulses:
                if (
                    pulse[0] in cycles and pulse[2] == "high"
                    and cycles[pulse[0]] is None
                ):
                    cycles[pulse[0]] = presses
    
                if pulse[1] in modules:
                    for next_pulse in modules[pulse[1]].recieve(
                        pulse[0], pulse[2]
                    ):
                        next_pulses.append((pulse[1],) + next_pulse)
    
                if presses <= 1000:
                    if pulse[2] == "low":
                        low_pulses += 1
                    else:
                        high_pulses += 1
    
            pulses = next_pulses
    
        presses += 1
    
    print(f"Part 1: {low_pulses * high_pulses}")
    print(f"Part 2: {lcm(*cycles.values())}")
    
    1 vote
  3. Comment on Day 19: Aplenty in ~comp.advent_of_code

    lily
    (edited )
    Link
    Today's problem was a lot of fun. I was glad for the easier problem after not having time to finish yesterday's part 2. This one reminded me a bit of day 5, what with having to use ranges for part...

    Today's problem was a lot of fun. I was glad for the easier problem after not having time to finish yesterday's part 2. This one reminded me a bit of day 5, what with having to use ranges for part 2. My solution ended up being pretty long, but I think it's reasonably efficient.

    Solution
    # Advent of Code 2023
    # Day 19: Aplenty
    
    from copy import deepcopy
    from functools import reduce
    from json import loads
    from operator import mul
    from re import sub
    
    workflows = {}
    parts = []
    
    with open("inputs/day_19.txt") as file:
        half = 0
    
        for line in file:
            if half == 0:
                if line == "\n":
                    half = 1
                else:
                    halves = line.split("{")
                    halves[1] = halves[1].rstrip("\n")[:-1]
    
                    conditions = []
    
                    for condition in halves[1].split(","):
                        if condition == "A":
                            conditions.append(True)
                        elif condition == "R":
                            conditions.append(False)
                        elif ":" not in condition:
                            conditions.append(condition)
                        else:
                            condition_halves = condition.split(":")
    
                            if condition_halves[1] == "A":
                                result = True
                            elif condition_halves[1] == "R":
                                result = False
                            else:
                                result = condition_halves[1]
    
                            conditions.append((
                                condition_halves[0][1],
                                "xmas".index(condition_halves[0][0]),
                                int(condition_halves[0][2:]),
                                result
                            ))
    
                    workflows[halves[0]] = conditions
            else:
                parts.append(list(loads(sub(
                    r"([xmas])=", r'"\g<1>": ', line.rstrip("\n")
                )).values()))
    
    rating_sum = 0
    
    for part in parts:
        workflow = workflows["in"]
        done = False
    
        while not done:
            for condition in workflow:
                if condition == True:
                    rating_sum += sum(part)
                    done = True
                    break
                elif condition == False:
                    done = True
                    break
                elif isinstance(condition, str):
                    workflow = workflows[condition]
                else:
                    if condition[0] == "<":
                        pass_ = part[condition[1]] < condition[2]
                    else:
                        pass_ = part[condition[1]] > condition[2]
    
                    if pass_:
                        if condition[3] == True:
                            rating_sum += sum(part)
                            done = True
                        elif condition[3] == False:
                            done = True
                        else:
                            workflow = workflows[condition[3]]
    
                        break
    
    print(f"Part 1: {rating_sum}")
    
    states = (["in", [1, 4000], [1, 4000], [1, 4000], [1, 4000]],)
    combinations = 0
    
    while states:
        new_states = []
    
        for state in states:
            for condition in workflows[state[0]]:
                if condition == True:
                    combinations += reduce(
                        mul, (range_[1] - range_[0] + 1 for range_ in state[1:])
                    )
    
                    break
                elif isinstance(condition, str):
                    state[0] = condition
                    new_states.append(state)
                    break
                elif condition != False:
                    state_pass = deepcopy(state)
                    range_pass = state_pass[condition[1] + 1]
    
                    range_pass[condition[0] == "<"] = (
                        condition[2] + (1, -1)[condition[0] == "<"]
                    )
    
                    state[condition[1] + 1][condition[0] == ">"] = condition[2]
    
                    if range_pass[0] <= range_pass[1]:
                        if condition[3] == True:
                            combinations += reduce(mul, (
                                range_[1] - range_[0] + 1
                                for range_ in state_pass[1:]
                            ))
                        elif condition[3] != False:
                            state_pass[0] = condition[3]
                            new_states.append(state_pass)
    
        states = new_states
    
    print(f"Part 2: {combinations}")
    
    2 votes
  4. Comment on Tildes Game Giveaway: Holiday 2023 in ~games

    lily
    Link Parent
    If Broken Age is still available that would be great. As for a holiday tradition, as a kid I always used to get two stockings: one on Christmas Day, and one on New Year's Eve. I never really...

    If Broken Age is still available that would be great. As for a holiday tradition, as a kid I always used to get two stockings: one on Christmas Day, and one on New Year's Eve. I never really thought much about it, to be honest. But other than that I don't think we ever did much interesting.

    1 vote
  5. Comment on Day 17: Clumsy Crucible in ~comp.advent_of_code

    lily
    Link
    Forgot to post this last night. It could've been a lot faster if I'd used another algorithm like A*, but I was lazy and Djikstra was fast enough that I didn't think I needed to bother implementing...

    Forgot to post this last night. It could've been a lot faster if I'd used another algorithm like A*, but I was lazy and Djikstra was fast enough that I didn't think I needed to bother implementing anything else. I was glad to finally see this year's requisite pathfinding problem, though I thought it wasn't an especially interesting example of one.

    Solution
    # Advent of Code 2023
    # Day 17: Clumsy Crucible
    
    import heapq
    
    with open("inputs/day_17.txt") as file:
        loss_map = [[int(char) for char in line.rstrip("\n")] for line in file]
        width = len(loss_map[0])
        height = len(loss_map)
    
    class Node:
        def __init__(self, x, y, direction, consecutive, heat_loss, part):
            self.x = x
            self.y = y
            self.direction = direction
            self.consecutive = consecutive
            self.heat_loss = heat_loss
            self.part = part
    
        def __lt__(self, other):
            return self.heat_loss < other.heat_loss
    
    heap = [Node(0, 0, None, 0, 0, 1), Node(0, 0, None, 0, 0, 2)]
    heapq.heapify(heap)
    seen = set()
    
    end_x = width - 1
    end_y = height - 1
    
    while heap:
        current = heapq.heappop(heap)
    
        if current.x == end_x and current.y == end_y:
            print(f"Part {current.part}: {current.heat_loss}")
    
            for node in heap.copy():
                if node.part == current.part:
                    heap.remove(node)
    
            continue
    
        for new_pos in (
            (current.x + 1, current.y, "right"),
            (current.x, current.y + 1, "down"),
            (current.x - 1, current.y, "left"),
            (current.x, current.y - 1, "up")
        ):
            if (
                0 <= new_pos[0] < width and 0 <= new_pos[1] < height
                and not (
                    current.direction == "right" and new_pos[2] == "left"
                    or current.direction == "down" and new_pos[2] == "up"
                    or current.direction == "left" and new_pos[2] == "right"
                    or current.direction == "up" and new_pos[2] == "down"
                )
                and not (
                    current.direction == new_pos[2]
                    and current.consecutive == (3, 10)[current.part - 1]
                )
            ):
                node = Node(
                    *new_pos,
                    (
                        current.consecutive + 1
                        if current.direction == new_pos[2] else 1
                    ),
                    current.heat_loss + loss_map[new_pos[1]][new_pos[0]],
                    current.part
                )
    
                if current.part == 2 and node.consecutive == 1:
                    if node.direction == "right":
                        change = (1, 0)
                    elif node.direction == "down":
                        change = (0, 1)
                    elif node.direction == "left":
                        change = (-1, 0)
                    else:
                        change = (0, -1)
    
                    for i in range(3):
                        node.x += change[0]
                        node.y += change[1]
    
                        try:
                            node.heat_loss += loss_map[node.y][node.x]
                        except IndexError:
                            pass
    
                    node.consecutive = 4
    
                values = (node.x, node.y, node.direction, node.consecutive)
    
                if values not in seen:
                    heapq.heappush(heap, node)
                    seen.add(values)
    
    1 vote
  6. Comment on Tildes Game Giveaway: Holiday 2023 in ~games

    lily
    Link Parent
    Xer Kzesfrums Istyms Hocktoo sounds lovely; those are all genres I enjoy. As for an upbeat song, I never get tired of the VVVVVV soundtrack. Thanks for your generosity!

    Xer Kzesfrums Istyms Hocktoo sounds lovely; those are all genres I enjoy. As for an upbeat song, I never get tired of the VVVVVV soundtrack. Thanks for your generosity!

    2 votes
  7. Comment on Tildes Game Giveaway: Holiday 2023 in ~games

    lily
    Link Parent
    I'd be interested in Rakuen and Cocoon. I'll do the opening quote from a game I just played recently, Slay the Princess (which I recommend highly if you haven't tried it, by the way): "Whatever...

    I'd be interested in Rakuen and Cocoon. I'll do the opening quote from a game I just played recently, Slay the Princess (which I recommend highly if you haven't tried it, by the way): "Whatever horrors you may find in these dark spaces, have heart and see them through. There are no premature endings. There are no wrong answers. There are only fresh perspectives and new beginnings. This is a love story."

    Thanks for doing this!

    1 vote
  8. Comment on Day 16: The Floor Will Be Lava in ~comp.advent_of_code

    lily
    Link
    Only just got to this problem now, but it really didn't take long. Didn't have time to make my solution any faster, but it's not slow enough to be a big deal, so I don't mind keeping it like this....

    Only just got to this problem now, but it really didn't take long. Didn't have time to make my solution any faster, but it's not slow enough to be a big deal, so I don't mind keeping it like this. I was honestly a bit disappointed the naïve solution was enough for today, but to be fair, if it wasn't, I might not have been able to finish the problem before midnight, so I guess I should be grateful. Hopefully tomorrow is a bit more interesting.

    Solution
    # Advent of Code 2023
    # Day 16: The Floor Will Be Lava
    
    with open("inputs/day_16.txt") as file:
        machine = [list(line.rstrip("\n")) for line in file]
        width = len(machine[0])
        height = len(machine)
    
    def energized_tiles(x, y, direction):
        beams = [[x, y, direction]]
        seen_beams = set()
        energized = set()
    
        while beams:
            for beam in beams.copy():
                if (
                    beam[0] < 0 or beam[0] >= width
                    or beam[1] < 0 or beam[1] >= height
                    or tuple(beam) in seen_beams
                ):
                    beams.remove(beam)
                    continue
    
                seen_beams.add(tuple(beam))
                energized.add((beam[0], beam[1]))
    
                tile = machine[beam[1]][beam[0]]
    
                if tile == "/":
                    if beam[2] == "right":
                        beam[2] = "up"
                    elif beam[2] == "down":
                        beam[2] = "left"
                    elif beam[2] == "left":
                        beam[2] = "down"
                    else:
                        beam[2] = "right"
                elif tile == "\\":
                    if beam[2] == "right":
                        beam[2] = "down"
                    elif beam[2] == "down":
                        beam[2] = "right"
                    elif beam[2] == "left":
                        beam[2] = "up"
                    else:
                        beam[2] = "left"
                elif tile == "|" and beam[2] in ("right", "left"):
                    beam[2] = "down"
                    beams.append([beam[0], beam[1] - 1, "up"])
                elif tile == "-" and beam[2] in ("down", "up"):
                    beam[2] = "right"
                    beams.append([beam[0] - 1, beam[1], "left"])
    
                if beam[2] == "right":
                    beam[0] += 1
                elif beam[2] == "down":
                    beam[1] += 1
                elif beam[2] == "left":
                    beam[0] -= 1
                else:
                    beam[1] -= 1
    
        return len(energized)
    
    print(f"Part 1: {energized_tiles(0, 0, 'right')}")
    
    starts = []
    
    for x in range(width):
        starts.append([x, 0, "down"])
        starts.append([x, height - 1, "up"])
    
    for y in range(height):
        starts.append([0, y, "right"])
        starts.append([width - 1, y, "left"])
    
    print(f"Part 2: {max(energized_tiles(*start) for start in starts)}")
    
    1 vote
  9. Comment on Day 15: Lens Library in ~comp.advent_of_code

    lily
    Link
    Surprisingly easy for day 15, though I wasn't quite as fast as I would've liked (I'm posting quite a bit later, but I finished in around 22 minutes or so). I expected the part 2 twist to be more...

    Surprisingly easy for day 15, though I wasn't quite as fast as I would've liked (I'm posting quite a bit later, but I finished in around 22 minutes or so). I expected the part 2 twist to be more difficult than it ended up being.

    Solution
    # Advent of Code 2023
    # Day 15: Lens Library
    
    def hash_(s):
        current = 0
    
        for char in s:
            current += ord(char)
            current *= 17
            current %= 256
    
        return current
    
    hash_sum = 0
    boxes = [[] for _ in range(256)]
    
    with open("inputs/day_15.txt") as file:
        for step in file.read().rstrip("\n").split(","):
            hash_sum += hash_(step)
    
            label = "".join(char for char in step if char.isalpha())
            label_len = len(label)
    
            box = boxes[hash_(label)]
    
            if step[label_len] == "=":
                existing = False
    
                for lens in box:
                    if lens[0] == label:
                        lens[1] = int(step[label_len + 1])
                        existing = True
                        break
    
                if not existing:
                    box.append([label, int(step[label_len + 1])])
            else:
                for lens in box:
                    if lens[0] == label:
                        box.remove(lens)
                        break
    
    print(f"Part 1: {hash_sum}")
    
    focusing_power = 0
    
    for i, box in enumerate(boxes):
        for j, lens in enumerate(box):
            old = focusing_power
            focusing_power += (i + 1) * (j + 1) * lens[1]
    
    print(f"Part 2: {focusing_power}")
    
    1 vote
  10. Comment on Day 14: Reflector Dish in ~comp.advent_of_code

    lily
    Link
    Forgot to post this last night. I wasn't quite able to get top 1000 on part 2, but thought I did pretty well otherwise. Part 2 was pretty simple once I realized I needed to detect cycles. Solution...

    Forgot to post this last night. I wasn't quite able to get top 1000 on part 2, but thought I did pretty well otherwise. Part 2 was pretty simple once I realized I needed to detect cycles.

    Solution
    # Advent of Code 2023
    # Day 14: Parabolic Reflector Dish
    
    with open("inputs/day_14.txt") as file:
        platform = [list(line.rstrip("\n")) for line in file.readlines()]
        size = len(platform)
    
    def total_load(platform):
        total_load = 0
    
        for y, row in enumerate(platform):
            for char in row:
                if char == "O":
                    total_load += size - y
    
        return total_load
    
    seen = {}
    part_1_finished = False
    iteration = 0
    
    while iteration < 1000000000:
        for _ in range(4):
            for x in range(size):
                for y in range(1, size):
                    if platform[y][x] == "O" and platform[y - 1][x] == ".":
                        for new_y in range(y - 1, -1, -1):
                            if platform[new_y][x] != ".":
                                new_y += 1
                                break
    
                        platform[y][x] = "."
                        platform[new_y][x] = "O"
    
            if not part_1_finished:
                print(f"Part 1: {total_load(platform)}")
                part_1_finished = True
    
            platform = [list(reversed(row)) for row in zip(*platform)]
    
        key = "".join("".join(row) for row in platform)
    
        if key in seen:
            cycle_length = iteration - seen[key]
            iteration += (1000000000 - iteration) // cycle_length * cycle_length
        else:
            seen[key] = iteration
    
        iteration += 1
    
    print(f"Part 2: {total_load(platform)}")
    
    1 vote
  11. Comment on Day 12: Hot Spring in ~comp.advent_of_code

    lily
    Link
    Wondering if anyone could point me in the right direction for this day's part 2? I tried for hours to get it working but hadn't ever done dynamic programming before and so wasn't able to figure...

    Wondering if anyone could point me in the right direction for this day's part 2? I tried for hours to get it working but hadn't ever done dynamic programming before and so wasn't able to figure out what to memoize to make my program fast enough.

    2 votes
  12. Comment on Day 13: Point of Incidence in ~comp.advent_of_code

    lily
    Link
    Was glad for the break after how hard yesterday was (I wasn't able to solve part 2 yesterday, unfortunately, which is why I didn't post my solution). I could probably optimize this quite a bit...

    Was glad for the break after how hard yesterday was (I wasn't able to solve part 2 yesterday, unfortunately, which is why I didn't post my solution). I could probably optimize this quite a bit further, but I don't really see the need to; it's plenty fast enough as it is. Hopefully the difficulty is a bit more consistent from here on out.

    Solution
    # Advent of Code 2023
    # Day 13: Point of Incidence
    
    def find_vertical_reflection(pattern, compare):
        for x in range(1, len(pattern[0])):
            left_side = [row[:x] for row in pattern]
            right_side = [list(reversed(row[x:])) for row in pattern]
    
            left_width = len(left_side[0])
            right_width = len(right_side[0])
    
            if left_width > right_width:
                difference = left_width - right_width
                left_side_adjusted = [row[difference:] for row in left_side]
                right_side_adjusted = right_side
            elif right_width > left_width:
                difference = right_width - left_width
                left_side_adjusted = left_side
                right_side_adjusted = [row[difference:] for row in right_side]
            else:
                left_side_adjusted = left_side
                right_side_adjusted = right_side
    
            if compare(left_side_adjusted, right_side_adjusted):
                return left_width
    
        return 0
    
    def smudged_compare(left_side, right_side):
        left_side = [char for row in left_side for char in row]
        right_side = [char for row in right_side for char in row]
    
        return sum(
            left_side[i] != right_side[i] for i in range(len(left_side))
        ) == 1
    
    notes_sum = 0
    notes_sum_smudged = 0
    
    with open("inputs/day_13.txt") as file:
        for pattern in file.read().split("\n\n"):
            pattern = [list(row.rstrip("\n")) for row in pattern.splitlines()]
    
            notes_sum += find_vertical_reflection(pattern, list.__eq__)
            notes_sum += find_vertical_reflection(
                [list(row) for row in list(zip(*pattern))[::-1]], list.__eq__
            ) * 100
    
            notes_sum_smudged += find_vertical_reflection(pattern, smudged_compare)
            notes_sum_smudged += find_vertical_reflection(
                [list(row) for row in list(zip(*pattern))[::-1]], smudged_compare
            ) * 100
    
    print(f"Part 1: {notes_sum}")
    print(f"Part 2: {notes_sum_smudged}")
    
    2 votes
  13. Comment on Day 11: Cosmic Expansion in ~comp.advent_of_code

    lily
    Link
    Had an irritating error that prevented me from getting a leaderboard spot on part 2, but other than that, today was a nice break after yesterday. I did part 1 pretty naïvely, so had to rewrite my...

    Had an irritating error that prevented me from getting a leaderboard spot on part 2, but other than that, today was a nice break after yesterday. I did part 1 pretty naïvely, so had to rewrite my solution for part 2.

    Solution
    # Advent of Code 2023
    # Day 11: Cosmic Expansion
    
    from itertools import combinations
    
    with open("inputs/day_11.txt") as file:
        image = [list(line[:-1]) for line in file.readlines()]
    
    galaxies = []
    
    for y, row in enumerate(image):
        for x, tile in enumerate(row):
            if tile == "#":
                galaxies.append([x, y])
    
    moved_galaxies_part_1 = [galaxy.copy() for galaxy in galaxies]
    moved_galaxies_part_2 = [galaxy.copy() for galaxy in galaxies]
    
    for y, row in enumerate(image):
        if "#" not in row:
            for i, galaxy in enumerate(galaxies):
                if galaxy[1] > y:
                    moved_galaxies_part_1[i][1] += 1
                    moved_galaxies_part_2[i][1] += 999999
    
    for x in range(len(image[0])):
        if not any(image[y][x] == "#" for y in range(len(image))):
            for i, galaxy in enumerate(galaxies):
                if galaxy[0] > x:
                    moved_galaxies_part_1[i][0] += 1
                    moved_galaxies_part_2[i][0] += 999999
    
    distance_sum_part_1 = 0
    
    for pair in combinations(moved_galaxies_part_1, 2):
        distance_sum_part_1 += (
            abs(pair[1][0] - pair[0][0]) + abs(pair[1][1] - pair[0][1])
        )
    
    print(f"Part 1: {distance_sum_part_1}")
    
    distance_sum_part_2 = 0
    
    for pair in combinations(moved_galaxies_part_2, 2):
        distance_sum_part_2 += (
            abs(pair[1][0] - pair[0][0]) + abs(pair[1][1] - pair[0][1])
        )
    
    print(f"Part 2: {distance_sum_part_2}")
    
    2 votes
  14. Comment on Day 10: Pipe Maze in ~comp.advent_of_code

    lily
    Link
    I was a complete idiot solving this one. I spent multiple hours trying to solve part 2 without increasing the resolution of the grid and without raycasting like @Hvv (I was basically special...

    I was a complete idiot solving this one. I spent multiple hours trying to solve part 2 without increasing the resolution of the grid and without raycasting like @Hvv (I was basically special casing the individual combinations of characters that could be "squeezed" through; it was horrible), and ended up writing an insanely long and complicated program that didn't actually work. I eventually figured out that I could increase the resolution, and once I did I solved part 2 in about 10 minutes. If I'd figured that out earlier I might've been able to snag a leaderboard spot. But oh well. At least I got top 300 for part 1. I'm not very happy with my solution (it's slow and inelegant), but it works and I don't think I have the energy to improve it any further, haha.

    Solution
    # Advent of Code 2023
    # Day 10: Pipe Maze
    
    with open("inputs/day_10.txt") as file:
        sketch = [list(line[:-1]) for line in file.readlines()]
    
    for y, row in enumerate(sketch):
        for x, tile in enumerate(row):
            if tile == "S":
                start_x = x
                start_y = y
    
    width = len(sketch[0])
    height = len(sketch)
    
    current_x = start_x
    current_y = start_y
    
    if current_x < len(sketch[0]) and sketch[current_y][current_x + 1] in "-J7":
        current_x += 1
        current_dir = "right"
    elif current_y < height and sketch[current_y + 1][current_x] in "|LJ":
        current_y += 1
        current_dir = "down"
    elif current_x > 0 and sketch[current_y][current_x - 1] in "-LF":
        current_x -= 1
        current_dir = "left"
    else:
        current_y -= 1
        current_dir = "up"
    
    start_dir = current_dir
    loop = [(current_x, current_y)]
    
    while current_x != start_x or current_y != start_y:
        tile = sketch[current_y][current_x]
    
        if tile == "|":
            if current_dir == "down":
                current_y += 1
            elif current_dir == "up":
                current_y -= 1
        elif tile == "-":
            if current_dir == "right":
                current_x += 1
            elif current_dir == "left":
                current_x -= 1
        elif tile == "L":
            if current_dir == "down":
                current_x += 1
                current_dir = "right"
            elif current_dir == "left":
                current_y -= 1
                current_dir = "up"
        elif tile == "J":
            if current_dir == "right":
                current_y -= 1
                current_dir = "up"
            elif current_dir == "down":
                current_x -= 1
                current_dir = "left"
        elif tile == "7":
            if current_dir == "right":
                current_y += 1
                current_dir = "down"
            elif current_dir == "up":
                current_x -= 1
                current_dir = "left"
        elif tile == "F":
            if current_dir == "left":
                current_y += 1
                current_dir = "down"
            elif current_dir == "up":
                current_x += 1
                current_dir = "right"
    
        loop.append((current_x, current_y))
    
    print(f"Part 1: {len(loop) // 2}")
    
    if current_dir == "right":
        if start_dir == "right":
            sketch[start_y][start_x] = "-"
        elif start_dir == "down":
            sketch[start_y][start_x] = "7"
        elif start_dir == "up":
            sketch[start_y][start_x] = "J"
    elif current_dir == "down":
        if start_dir == "right":
            sketch[start_y][start_x] = "L"
        elif start_dir == "down":
            sketch[start_y][start_x] = "|"
        elif start_dir == "left":
            sketch[start_y][start_x] = "J"
    elif current_dir == "left":
        if start_dir == "down":
            sketch[start_y][start_x] = "F"
        elif start_dir == "left":
            sketch[start_y][start_x] = "-"
        elif start_dir == "up":
            sketch[start_y][start_x] = "L"
    elif start_dir == "right":
        sketch[start_y][start_x] = "F"
    elif start_dir == "left":
        sketch[start_y][start_x] = "7"
    else:
        sketch[start_y][start_x] = "|"
    
    detailed = [[False for _ in range(width * 3)] for _ in range(height * 3)]
    
    for y, row in enumerate(sketch):
        for x, tile in enumerate(row):
            if (x, y) in loop:
                if tile == "|":
                    detailed[y * 3][x * 3:x * 3 + 3] = False, True, False
                    detailed[y * 3 + 1][x * 3:x * 3 + 3] = False, True, False
                    detailed[y * 3 + 2][x * 3:x * 3 + 3] = False, True, False
                elif tile == "-":
                    detailed[y * 3][x * 3:x * 3 + 3] = False, False, False
                    detailed[y * 3 + 1][x * 3:x * 3 + 3] = True, True, True
                    detailed[y * 3 + 2][x * 3:x * 3 + 3] = False, False, False
                elif tile == "L":
                    detailed[y * 3][x * 3:x * 3 + 3] = False, True, False
                    detailed[y * 3 + 1][x * 3:x * 3 + 3] = False, True, True
                    detailed[y * 3 + 2][x * 3:x * 3 + 3] = False, False, False
                elif tile == "J":
                    detailed[y * 3][x * 3:x * 3 + 3] = False, True, False
                    detailed[y * 3 + 1][x * 3:x * 3 + 3] = True, True, False
                    detailed[y * 3 + 2][x * 3:x * 3 + 3] = False, False, False
                elif tile == "7":
                    detailed[y * 3][x * 3:x * 3 + 3] = False, False, False
                    detailed[y * 3 + 1][x * 3:x * 3 + 3] = True, True, False
                    detailed[y * 3 + 2][x * 3:x * 3 + 3] = False, True, False
                elif tile == "F":
                    detailed[y * 3][x * 3:x * 3 + 3] = False, False, False
                    detailed[y * 3 + 1][x * 3:x * 3 + 3] = False, True, True
                    detailed[y * 3 + 2][x * 3:x * 3 + 3] = False, True, False
    
    queue = []
    
    for y, row in enumerate(detailed):
        if y in (0, height * 3 - 1):
            search = range(width * 3)
        else:
            search = (0, width * 3 - 1)
    
        for x in search:
            if not row[x]:
                queue.append((x, y))
    
    visited = set()
    
    while queue:
        pos = queue.pop(0)
    
        for new_pos in (
            (pos[0] + 1, pos[1]),
            (pos[0], pos[1] + 1),
            (pos[0] - 1, pos[1]),
            (pos[0], pos[1] - 1)
        ):
            try:
                if not detailed[new_pos[1]][new_pos[0]] and new_pos not in visited:
                    queue.append(new_pos)
                    visited.add(new_pos)
            except IndexError:
                pass
    
    inside = 0
    
    for y in range(height):
        for x in range(width):
            if (
                (x * 3 + 1, y * 3 + 1) not in visited
                and not detailed[y * 3 + 1][x * 3 + 1]
            ):
                inside += 1
    
    print(f"Part 2: {inside}")
    
    1 vote
  15. Comment on Day 9: Mirage Maintenance in ~comp.advent_of_code

    lily
    Link Parent
    Now that I think about it, part of what made this problem feel so easy was that the entire solution was basically described in the problem text itself. I feel like that wasn't really necessary...

    Now that I think about it, part of what made this problem feel so easy was that the entire solution was basically described in the problem text itself. I feel like that wasn't really necessary...

    2 votes
  16. Comment on Day 9: Mirage Maintenance in ~comp.advent_of_code

    lily
    Link
    Well, this one was... weirdly easy. And part 2 was almost exactly the same difficulty as part 1. It took me about a minute. I feel this problem would be much better suited for around day 4....

    Well, this one was... weirdly easy. And part 2 was almost exactly the same difficulty as part 1. It took me about a minute. I feel this problem would be much better suited for around day 4.

    Solution
    # Advent of Code 2023
    # Day 9: Mirage Maintenance
    
    histories = []
    
    with open("inputs/day_9.txt") as file:
        for line in file:
            histories.append([int(n) for n in line.split(" ")])
    
    next_prediction_sum = 0
    prev_prediction_sum = 0
    
    for history in histories:
        right_ends = [history[-1]]
        left_ends = [history[0]]
    
        working = history.copy()
    
        while True:
            for i in range(len(working) - 1, -1, -1):
                if i >= 1:
                    working[i] = working[i] - working[i - 1]
    
            working = working[1:]
    
            if all(n == working[0] for n in working):
                next_prediction = working[-1]
                prev_prediction = working[0]
    
                for n in reversed(right_ends):
                    next_prediction += n
    
                for n in reversed(left_ends):
                    prev_prediction = n - prev_prediction
    
                next_prediction_sum += next_prediction
                prev_prediction_sum += prev_prediction
    
                break
            else:
                right_ends.append(working[-1])
                left_ends.append(working[0])
    
    print(f"Part 1: {next_prediction_sum}")
    print(f"Part 2: {prev_prediction_sum}")
    
    1 vote
  17. Comment on Day 8: Haunted Wasteland in ~comp.advent_of_code

    lily
    Link
    Really fun problem, and felt just about the right level of difficulty for this far into the month. The solution was clever, but not too hard to figure out - I like solutions like that, since they...

    Really fun problem, and felt just about the right level of difficulty for this far into the month. The solution was clever, but not too hard to figure out - I like solutions like that, since they make me feel smart ;)

    I solved the problem a while before posting this, but took some time to assemble a nice program that solves both parts at once and to write actual input parsing (I initially just copied the input into my program and used a regex to convert it to a Python dictionary). I got top 400 on part 1, but unfortunately wasn't quite so fast doing part 2.

    (Also, yes, I know the input parsing is horrible. It's just Advent of Code though, it's fine I promise)

    Solution
    # Advent of Code 2023
    # Day 8: Haunted Wasteland
    
    import math
    
    graph = {}
    
    with open("inputs/day_8.txt") as file:
        for i, line in enumerate(file):
            if i == 0:
                instructions = line.rstrip("\n")
            elif i > 1:
                graph[line[:3]] = (line[7:10], line[12:15])
    
    def next_location(graph, location):
        return graph[location]["LR".index(instructions[steps % len(instructions)])]
    
    steps = 0
    location = "AAA"
    
    while location != "ZZZ":
        location = next_location(graph, location)
        steps += 1
    
    print(f"Part 1: {steps}")
    
    locations = [location for location in graph if location[-1] == "A"]
    steps_per_location = []
    
    for i in range(len(locations)):
        steps = 0
        z_count = 0
    
        while z_count < 2:
            locations[i] = next_location(graph, locations[i])
            steps += 1
    
            if locations[i][-1] == "Z":
                if z_count == 0:
                    steps = 0
    
                z_count += 1
    
        steps_per_location.append(steps)
    
    print(f"Part 2: {math.lcm(*steps_per_location)}")
    
    3 votes
  18. Comment on Day 7: Camel Cards in ~comp.advent_of_code

    lily
    Link
    This was a fun problem, though I think I might have overcomplicated things a bit. I always like to try to write a single program that does both part 1 and 2 at once if possible, and that was a...

    This was a fun problem, though I think I might have overcomplicated things a bit. I always like to try to write a single program that does both part 1 and 2 at once if possible, and that was a little trickier than usual today. Nevertheless, I'm glad the problems are more interesting now. Today's still wasn't too difficult (especially compared to day 5), but it wasn't just parsing the input like the first few. Part 2 probably could have changed things up a bit more, though.

    Solution
    # Advent of Code 2023
    # Day 7: Camel Cards
    
    from copy import deepcopy
    from functools import cmp_to_key
    
    special_cards = {
        "T": 10,
        "J": 11,
        "Q": 12,
        "K": 13,
        "A": 14
    }
    
    hands_part_1 = []
    
    with open("inputs/day_7.txt") as file:
        for line in file:
            halves = line.split(" ")
            hands_part_1.append({
                "cards": [
                    int(card) if card in "123456789" else special_cards[card]
                    for card in halves[0]
                ],
                "bid": int(halves[1])
            })
    
    hands_part_2 = deepcopy(hands_part_1)
    
    for hand in hands_part_2:
        hand["cards"] = [0 if card == 11 else card for card in hand["cards"]]
    
    def find_type(cards):
        set_ = set(cards)
        unique_cards = len(set_)
    
        if unique_cards == 1:
            return 0
        elif unique_cards == 2:
            if cards.count(cards[0]) in (1, 4):
                return 1
            else:
                return 2
        elif unique_cards == 3:
            if any(cards.count(card) == 3 for card in set_):
                return 3
            elif any(cards.count(card) == 2 for card in set_):
                return 4
        elif unique_cards == 4:
            return 5
        else:
            return 6
    
    for hand in hands_part_1:
        hand["type"] = find_type(hand["cards"])
    
    for hand in hands_part_2:
        if 0 in hand["cards"]:
            hand["type"] = None
    
            for new_card in range(15):
                if new_card != 0:
                    modified_cards = [
                        new_card if card == 0 else card for card in hand["cards"]
                    ]
    
                    type_ = find_type(modified_cards)
    
                    if hand["type"] is None or type_ < hand["type"]:
                        hand["type"] = type_
        else:
            hand["type"] = find_type(hand["cards"])
    
    def compare_hands(hand_1, hand_2):
        if hand_1["type"] < hand_2["type"]:
            return 1
        elif hand_2["type"] < hand_1["type"]:
            return -1
        else:
            for card_1, card_2 in zip(hand_1["cards"], hand_2["cards"]):
                if card_1 > card_2:
                    return 1
                elif card_2 > card_1:
                    return -1
    
            return 0
    
    key = cmp_to_key(compare_hands)
    hands_part_1.sort(key=key)
    hands_part_2.sort(key=key)
    
    print(f"""Part 1: {
        sum(hand['bid'] * (i + 1) for i, hand in enumerate(hands_part_1))
    }""")
    
    print(f"""Part 2: {
        sum(hand['bid'] * (i + 1) for i, hand in enumerate(hands_part_2))
    }""")
    
    2 votes
  19. Comment on Day 5: If You Give A Seed A Fertilizer in ~comp.advent_of_code

    lily
    Link
    I was going to wait to post here until I wrote a solution that wasn't brute force, but I haven't been able to. I figured out what I'm supposed to do, but for some reason I can only get my program...

    I was going to wait to post here until I wrote a solution that wasn't brute force, but I haven't been able to. I figured out what I'm supposed to do, but for some reason I can only get my program to work with either the test input or the proper input, not both. The only way I can get the correct answer for the proper input is by forcing my program to have what I believe is the incorrect behavior? It's really weird. Since it's been a day and I haven't figured it out, though, I'll just post my brute force solution from last night.

    Solution
    # Advent of Code 2023
    # Day 5: If You Give A Seed A Fertilizer
    
    mappings = []
    
    with open("inputs/day_5.txt") as file:
        current = {}
        waiting_for_nums = True
    
        for i, line in enumerate(file):
            if i == 0:
                seeds = [int(n) for n in line.split(": ")[1].split()]
            else:
                if line[0] in "0123456789":
                    nums = [int(n) for n in line.split()]
                    current[range(nums[1], nums[1] + nums[2])] = nums[0] - nums[1]
                    waiting_for_nums = False
                elif not waiting_for_nums:
                    mappings.append(current)
                    current = {}
                    waiting_for_nums = True
    
        mappings.append(current)
    
    def find_lowest_location(seeds, mappings):
        lowest_location = None
    
        for seed in seeds:
            current = seed
    
            for depth in mappings:
                for range_ in depth:
                    if current in range_:
                        current += depth[range_]
                        break
    
            if lowest_location is None or current < lowest_location:
                lowest_location = current
    
        return lowest_location
    
    print(f"Part 1: {find_lowest_location(seeds, mappings)}")
    
    def seeds_part_2():
        for start, length in zip(seeds[0::2], seeds[1::2]):
            for seed in range(start, start + length):
                yield seed
    
    print(f"Part 2: {find_lowest_location(seeds_part_2(), mappings)}")
    
    1 vote
  20. Comment on Day 6: Wait For It in ~comp.advent_of_code

    lily
    Link
    I wasted time on parsing or I would've done better. Should've just copied the values straight into my code and I probably could've gotten a leaderboard spot. But oh well, can't win 'em all....

    I wasted time on parsing or I would've done better. Should've just copied the values straight into my code and I probably could've gotten a leaderboard spot. But oh well, can't win 'em all.

    Solution
    # Advent of Code 2023
    # Day 6: Wait For It
    
    from functools import reduce
    from operator import mul
    
    with open("inputs/day_6.txt") as file:
        line_1 = next(file)
        line_2 = next(file)
    
        races_part_1 = list(zip(
            (int(n) for n in line_1.split()[1:]),
            (int(n) for n in line_2.split()[1:])
        ))
    
        race_part_2 = (
            int("".join(line_1.split()[1:])),
            int("".join(line_2.split()[1:]))
        )
    
    def find_ways(race):
        ways = 0
    
        for hold_duration in range(1, race[0]):
            distance = (race[0] - hold_duration) * hold_duration
    
            if distance > race[1]:
                ways += 1
    
        return ways
    
    print(f"Part 1: {reduce(mul, (find_ways(race) for race in races_part_1))}")
    print(f"Part 2: {find_ways(race_part_2)}")
    
    1 vote