advent-of-code 9 min read

[Day 18] Lavaduct Lagoon // Advent of Code 2023 (Python)

Solving Advent of Code 2023 Day 18 in Python with the Shoelace formula and Pick's theorem, then scaling up to huge hex-encoded dig plans in Part 2.

Lavaduct Lagoon (via DALL-E 3)

Lavaduct Lagoon (via DALL-E 3)

Advent of Code
Thanks to your efforts, the machine parts factory is one of the first factories up and running since the lavafall came…adventofcode.com


Part One

Thanks to your efforts, the machine parts factory is one of the first factories up and running since the lavafall came back. However, to catch up with the large backlog of parts requests, the factory will also need a large supply of lava for a while; the Elves have already started creating a large lagoon nearby for this purpose.

However, they aren’t sure the lagoon will be big enough; they’ve asked you to take a look at the dig plan (your puzzle input). For example:

R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)

The digger starts in a 1 meter cube hole in the ground. They then dig the specified number of meters up (U), down (D), left (L), or right (R), clearing full 1 meter cubes as they go. The directions are given as seen from above, so if “up” were north, then “right” would be east, and so on. Each trench is also listed with the color that the edge of the trench should be painted as an RGB hexadecimal color code.

When viewed from above, the above example dig plan would result in the following loop of trench (#) having been dug out from otherwise ground-level terrain (.):

#######
#…#
###…#
..#…#
..#…#
###.###
#…#..
##..###
.#…#
.######

At this point, the trench could contain 38 cubic meters of lava. However, this is just the edge of the lagoon; the next step is to dig out the interior so that it is one meter deep as well:

#######
#######
#######
..#####
..#####
#######
#####..
#######
.######
.######

Now, the lagoon can contain a much more respectable 62 cubic meters of lava. While the interior is dug out, the edges are also painted according to the color codes in the dig plan.

The Elves are concerned the lagoon won’t be large enough; if they follow their dig plan, how many cubic meters of lava could it hold?


So, we are given a dig plan for the lagoon, which looks like this:

R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)

So, this basically works in 3 steps:

  1. Dig the trenches as per each row of dig plan
  2. Paint the edges of trenches as per the dig plan
  3. Dig enclosed area by trench loop to form a lagoon

We can understand this better with the following visualization I created for my own understanding:

Visualizations make everything simple!

Visualizations make everything simple!

The Elves are concerned the lagoon won’t be large enough; if they follow their dig plan, how many cubic meters of lava could it hold?

Got it! So, let’s follow our visualization and write some code with the base test case!

💡I don’t see any use of RGB hexcodes for now, so we will park that. We may need it in part 2.

Let’s start one by one! First step is to implement dig_trench()

❗️A caveat I can observe is that we don’t really know the size of the site! All we know is that digger starts in a 1 meter cube hole in the ground.

Assuming we start from (0, 0) our size will depend on how much further RIGHT and DOWN we go! Something like row, col = max(DOWN), max(RIGHT) , we will figure out that.

Now, to parse dig_plan, we can just define:

And then, we can just iterate over the dig_plan and store the path in trench_path as a list of cordinates:

Our trench_path may have duplicate nodes based on our dig plan, so since each node is just 1 m³ deep, we can calculate it’s volume by just returning len(set(trench_path)) !

Let’s verify if this is correct, modifying our base test case:

[Running] python3 "/Users/jmalik/github/advent-of-code-2023/day_18/lavaduct_lagoon.py"
✅ get_trench_volume passed

[Done] exited with code=0 in 0.236 seconds

Perfect, now let’s implement dig_lagoon to convert our trench_path loop into a lagoon_area !

💭 So, whenver we have to find tiles enclosed within a loop, the best way I know to do so is to do the Flood Fill algorithm! It works something like:

  • start from 1 point inside the loop
  • use flood fill algorithm from this starting point to fill all reachable tiles
  • it starts at a tile, then recursively fills adjacent tiles until it hits the boundary (the trench_path in this case).
  • as the flood fill algorithm visits each tile, store these tiles in a set — lagoon_area

Here’s a quick visualization for this example:

Flood fill for the base case

Flood fill for the base case

But wait! We have seen this before! Remember Day 10?

[Day 10] Pipe Maze // Advent of Code 2023 (Python)
You use the hang glider to ride the hot air from Desert Island all the way up to the floating metal island. This island…medium.com

We had exactly the same problem, but here instead of area, we have to calculate the volume, but given the depth == 1 m³, the answer shall be equivalent to the area itself!

We can similarly use:

Shoelace formula - Wikipedia
The shoelace formula, also known as Gauss’s area formula and the surveyor’s formula, is a mathematical algorithm to…en.wikipedia.org

Pick’s theorem - Wikipedia
In geometry, Pick’s theorem provides a formula for the area of a simple polygon with integer vertex coordinates, in…en.wikipedia.org

And get number of tiles enclosed within trench_loop and then calculate lagoon’s volume!

Let’s experiment with that one first!

And…running our test case now:

[Running] python3 "/Users/jmalik/github/advent-of-code-2023/day_18/lavaduct_lagoon.py"
✅ get_trench_volume passed
✅ get_lagoon_volume passed

[Done] exited with code=0 in 0.305 seconds

Boom! Our hypothesis was correct, as we didn’t have to do the flood fill and Shoelace’s formula came to the rescue!

Let’s load up our puzzle input and see if it works!

❗️The only caveat with Shoelace’s formula is that it works for simple, non-intersecting polygon! That may cause a problem!

Let’s run this!

[Running] python3 "/Users/jmalik/github/advent-of-code-2023/day_18/lavaduct_lagoon.py"
❗️ Our lagoon can hold XXXXX m^3 of lava!

[Done] exited with code=0 in 0.312 seconds

We got an answer! 👏

Let’s put this in puzzle box and see if it is correct:

⭐️

⭐️

Here’s our code till now: 🤌

# Day 18: Lavaduct Lagoon

from enum import Enum

class Direction(Enum):
    UP = (-1, 0)
    DOWN = (1, 0)
    LEFT = (0, -1)
    RIGHT = (0, 1)

dig_plan_to_direction = {
    'U': Direction.UP,
    'D': Direction.DOWN,
    'L': Direction.LEFT,
    'R': Direction.RIGHT,
}

LAGOON_DEPTH = 1

def dig_trench(dig_plan):
    start_x, start_y = 0, 0
    trench_path = [(start_x, start_y)]

    for direction, distance, color in dig_plan:
        direction = dig_plan_to_direction[direction]
        for _ in range(int(distance)):
            dx, dy = direction.value
            start_x, start_y = start_x + dx, start_y + dy
            if (start_x, start_y) not in trench_path:
                trench_path.append((start_x, start_y))
    
    return trench_path

def get_trench_volume(trench_path):
    return len(trench_path) * LAGOON_DEPTH

def get_lagoon_volume(trench_path):
    lagoon_area = 0

    num_trench_tiles = len(trench_path)

    # Shoelace formula
    for i in range(num_trench_tiles):
        j = (i + 1) % num_trench_tiles

        lagoon_area += trench_path[i][0] * trench_path[j][1]
        lagoon_area -= trench_path[j][0] * trench_path[i][1]

    lagoon_area = abs(lagoon_area) / 2.0
    num_lagoon_tiles = lagoon_area - num_trench_tiles // 2 + 1

    return int((num_lagoon_tiles + num_trench_tiles) * LAGOON_DEPTH)

def test_lavaduct_lagoon():
    input_str = """R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)"""

    dig_plan = [line.split() for line in input_str.split('\n')]
    trench_path = dig_trench(dig_plan)
    trench_volume = get_trench_volume(trench_path)
    assert trench_volume == 38, f"Expected 38, got {trench_volume}"
    print("✅ get_trench_volume passed")

    lagoon_volume = get_lagoon_volume(trench_path)
    assert lagoon_volume == 62, f"Expected 62, got {lagoon_volume}"
    print("✅ get_lagoon_volume passed")

def part_one():
    with open('day_18/input.txt', 'r') as f:
        dig_plan = [line.split() for line in f.readlines()]

    trench_path = dig_trench(dig_plan)
    lagoon_volume = get_lagoon_volume(trench_path)
    print(f"❗️ Our lagoon can hold {lagoon_volume} m^3 of lava!")

if __name__ == "__main__":
    test_lavaduct_lagoon()
    part_one()

Part Two

The Elves were right to be concerned; the planned lagoon would be much too small.

After a few minutes, someone realizes what happened; someone swapped the color and instruction parameters when producing the dig plan. They don’t have time to fix the bug; one of them asks if you can extract the correct instructions from the hexadecimal codes.

Each hexadecimal code is six hexadecimal digits long. The first five hexadecimal digits encode the distance in meters as a five-digit hexadecimal number. The last hexadecimal digit encodes the direction to dig: 0 means R, 1 means D, 2 means L, and 3 means U.

So, in the above example, the hexadecimal codes can be converted into the true instructions:

#70c710 = R 461937

#0dc571 = D 56407

#5713f0 = R 356671

#d2c081 = D 863240

#59c680 = R 367720

#411b91 = D 266681

#8ceee2 = L 577262

#caa173 = U 829975

#1b58a2 = L 112010

#caa171 = D 829975

#7807d2 = L 491645

#a77fa3 = U 686074

#015232 = L 5411

#7a21e3 = U 500254

Digging out this loop and its interior produces a lagoon that can hold an impressive 952408144115 cubic meters of lava.

Convert the hexadecimal color codes into the correct instructions; if the Elves follow this new dig plan, how many cubic meters of lava could the lagoon hold?


Well, seems like the lagoon won’t hold all the lava!

Ofcourse, someone swapped the color and instruction parameters when producing the dig plan. 😪

Seems like we will need to extract instruction from what we thought to be hex codes for the colors first! Something like:

The new instruction set!

The new instruction set!

Well, looks like this might increase the scope of calculations, but good thing is that we were able to reduce this from a 2D Grid to a mathematical problem only!

I am pretty confident that our solution will scale, all we need to update is our parsing criteria!

First things first, let’s create another map to help us map the new dig directions:

We can now implement a new function — dig_trench_hex() which will parse the hexcode part of the input and then use it to create trench_path

Rest of the methods should be same more or less! Now let’s test our new code against the base test case:

While using the debugger, I can see the achilles heel of our code:

15 minutes later

15 minutes later

We have still not completed the first hex code in the test input 😅

We have still not completed the first hex code in the test input 😅

We need to rethink our approach as clearly we can’t have a trench path list with millions of enteries, it’s just not efficient as the larger a list becomes, the slower list operations become!

While we can have a band aid solution by trying numpy or pandas but I want to solve this using stdio itself.

Let’s think again! The bottleneck here is trying to generate these insanely long dig paths, what if we could just store the (start_x, start_y), (end_x, end_y) and use that to calculate everything else arithematically?

As anyway we just need the length of path to calculate the volume initially! The volume of lagoon might get tricky as we usually need vertex (cordinates) of the path, but we will figure that once we have the ranges for trench_path?!

Intoducing TrenchLine which is a subset of trench_path which simply calculates the end_x, end_y based on the direction and distance!

So, by changing our code and running it again, we can observe that we have a trench_path_ranges list with TrenchLine objects storing the range and direction for each dig line:

13 opearions, 13 lines with trench starting from 0, 0 and ending on 0, 0

13 opearions, 13 lines with trench starting from 0, 0 and ending on 0, 0

So, now that we have this, we can easily calculate the volume of trench as well:

Now, coming to lagoon, this gets a little tricky!

.

.

.

OH! EUREKA!

See, for Shoelace’s algorith we don’t really need each and every point on the path of the polygon! All we care is about the vertex nodes which incidentally we have captured in our trench_path_ranges

We can simplify transform this into a list of vertexes for the polygon by doing:

which gives us:

A list of 15 points encolsing our huge lagoon!

A list of 15 points encolsing our huge lagoon!

Now we can just use the same shoelace’s formula to solve this:

Let’s run the base test case?

[Running] python3 "/Users/jmalik/github/advent-of-code-2023/day_18/lavaduct_lagoon.py"
✅ get_trench_volume passed
✅ get_lagoon_volume passed

[Done] exited with code=0 in 0.256 seconds

Woohoo! Let’s load up our puzzle input and get the answer for part 2:

[Running] python3 "/Users/jmalik/github/advent-of-code-2023/day_18/lavaduct_lagoon.py"
❗️❗️ Our lagoon can actually hold XXXXXXXXXXXXXXX m^3 of lava!

[Done] exited with code=0 in 0.241 seconds

That is actually a longboi answer! But we were able to get it in just 0.241 seconds!

Let’s put this in our answer box and check if this is correct:

🌟

🌟

Here’s my rough code till now with a lot of repeated chunks:

# Day 18: Lavaduct Lagoon

from enum import Enum

class Direction(Enum):
    UP = (-1, 0)
    DOWN = (1, 0)
    LEFT = (0, -1)
    RIGHT = (0, 1)

dig_plan_to_direction = {
    "U": Direction.UP,
    "D": Direction.DOWN,
    "L": Direction.LEFT,
    "R": Direction.RIGHT,
}

hex_to_directions = {
    "0": Direction.RIGHT,
    "1": Direction.DOWN,
    "2": Direction.LEFT,
    "3": Direction.UP,
}

LAGOON_DEPTH = 1

class TrenchLine:
    def __init__(self, start_x, start_y, direction, distance):
        self.start_x = start_x
        self.start_y = start_y
        self.direction = direction
        self.distance = distance

        self.end_x = self.start_x + self.direction.value[0] * self.distance
        self.end_y = self.start_y + self.direction.value[1] * self.distance

    def __repr__(self):
        return f"({self.start_x}, {self.start_y}) -> ({self.end_x}, {self.end_y})"

def dig_trench_simple(dig_plan):
    start_x, start_y = 0, 0
    trench_path = [(start_x, start_y)]

    for direction, distance, hexcode in dig_plan:
        direction = dig_plan_to_direction[direction]
        for _ in range(int(distance)):
            dx, dy = direction.value
            start_x, start_y = start_x + dx, start_y + dy
            if (start_x, start_y) not in trench_path:
                trench_path.append((start_x, start_y))

    return trench_path

def parse_hex(hex_code):
    # remove parentheses
    hex_code = hex_code[1:-1]
    # remove hash
    hex_code = hex_code[1:]

    # split into dig_distance(5 chars), dig_direction(last char)
    dig_distance_hex, dig_direction_hex = hex_code[:-1], hex_code[-1]

    # convert dig_distance_hex to decimal
    dig_distance = int(dig_distance_hex, 16)
    # convert dig_direction_hex to direction
    dig_direction = hex_to_directions[dig_direction_hex]

    return dig_distance, dig_direction

def dig_trench_hex(dig_plan):
    start_x, start_y = 0, 0
    trench_path_ranges = []

    for _, _, hex_code in dig_plan:
        dig_distance, dig_direction = parse_hex(hex_code)
        trench_line = TrenchLine(start_x, start_y, dig_direction, dig_distance)
        trench_path_ranges.append(trench_line)
        start_x, start_y = trench_line.end_x, trench_line.end_y

    return trench_path_ranges

def get_trench_volume_hex(trench_path_ranges):
    trench_area = 0

    for trench_line in trench_path_ranges:
        trench_area += trench_line.distance

    return trench_area * LAGOON_DEPTH

def create_polygon_from_trench_lines(trench_path_ranges):
    # Assuming the trench path forms a simple, closed loop
    polygon_points = [(line.start_x, line.start_y) for line in trench_path_ranges]
    polygon_points.append(
        (trench_path_ranges[-1].end_x, trench_path_ranges[-1].end_y)
    )  # Closing the loop
    return polygon_points

def get_lagoon_volume_hex(trench_path_ranges):
    trench_volume = get_trench_volume_hex(trench_path_ranges)
    polygon_points = create_polygon_from_trench_lines(trench_path_ranges)

    num_of_vertex = len(polygon_points)
    lagoon_area = 0.0

    for i in range(num_of_vertex):
        j = (i + 1) % num_of_vertex
        lagoon_area += polygon_points[i][0] * polygon_points[j][1]
        lagoon_area -= polygon_points[j][0] * polygon_points[i][1]

    lagoon_area = abs(lagoon_area) / 2.0

    num_lagoon_tiles = lagoon_area - trench_volume // 2 + 1

    return int((num_lagoon_tiles + trench_volume) * LAGOON_DEPTH)

def get_trench_volume(trench_path):
    return len(trench_path) * LAGOON_DEPTH

def get_lagoon_volume(trench_path):
    lagoon_area = 0

    num_trench_tiles = len(trench_path)

    # Shoelace formula
    for i in range(num_trench_tiles):
        j = (i + 1) % num_trench_tiles

        lagoon_area += trench_path[i][0] * trench_path[j][1]
        lagoon_area -= trench_path[j][0] * trench_path[i][1]

    lagoon_area = abs(lagoon_area) / 2.0
    num_lagoon_tiles = lagoon_area - num_trench_tiles // 2 + 1

    return int((num_lagoon_tiles + num_trench_tiles) * LAGOON_DEPTH)

def test_lavaduct_lagoon():
    input_str = """R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)"""

    dig_plan = [line.split() for line in input_str.split("\n")]
    trench_path = dig_trench_simple(dig_plan)
    trench_volume = get_trench_volume(trench_path)
    assert trench_volume == 38, f"Expected 38, got {trench_volume}"
    print("✅ get_trench_volume passed")

    lagoon_volume = get_lagoon_volume(trench_path)
    assert lagoon_volume == 62, f"Expected 62, got {lagoon_volume}"
    print("✅ get_lagoon_volume passed")

def test_lavaduct_lagoon_hex():
    input_str = """R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)"""

    dig_plan = [line.split() for line in input_str.split("\n")]
    trench_path_ranges = dig_trench_hex(dig_plan)
    trench_volume = get_trench_volume_hex(trench_path_ranges)
    assert trench_volume == 6405262, f"Expected 6405262, got {trench_volume}"
    print("✅ get_trench_volume passed")

    lagoon_volume = get_lagoon_volume_hex(trench_path_ranges)
    assert lagoon_volume == 952408144115, f"Expected 952408144115, got {lagoon_volume}"
    print("✅ get_lagoon_volume passed")

def part_one():
    with open("day_18/input.txt", "r") as f:
        dig_plan = [line.split() for line in f.readlines()]

    trench_path = dig_trench_simple(dig_plan)
    lagoon_volume = get_lagoon_volume(trench_path)
    print(f"❗️ Our lagoon can hold {lagoon_volume} m^3 of lava!")

def part_two():
    with open("day_18/input.txt", "r") as f:
        dig_plan = [line.split() for line in f.readlines()]

    trench_path_ranges = dig_trench_hex(dig_plan)
    lagoon_volume = get_lagoon_volume_hex(trench_path_ranges)
    print(f"❗️❗️ Our lagoon can actually hold {lagoon_volume} m^3 of lava!")

if __name__ == "__main__":
    # test_lavaduct_lagoon()
    # part_one()

    # test_lavaduct_lagoon_hex()
    part_two()

Man! This felt really good. Time complexity optimizations are just black magic!

I see a couple of improvements in my code to make it even faster, let me do that and then share the pythonic code with you:

# Day 18: Lavaduct Lagoon

from enum import Enum

class Direction(Enum):
    UP = (-1, 0)
    DOWN = (1, 0)
    LEFT = (0, -1)
    RIGHT = (0, 1)

dig_plan_to_direction = {
    "U": Direction.UP,
    "D": Direction.DOWN,
    "L": Direction.LEFT,
    "R": Direction.RIGHT,
}

class TrenchLine:
    hex_to_directions = {
        "0": Direction.RIGHT,
        "1": Direction.DOWN,
        "2": Direction.LEFT,
        "3": Direction.UP,
    }

    def __init__(self, start_x, start_y, direction, distance):
        self.start_x = start_x
        self.start_y = start_y
        self.direction = direction
        self.distance = distance
        self.end_x, self.end_y = self.calculate_end_coordinates()

    def calculate_end_coordinates(self):
        return (
            self.start_x + self.direction.value[0] * self.distance,
            self.start_y + self.direction.value[1] * self.distance,
        )

    def __repr__(self):
        return f"({self.start_x}, {self.start_y}) -> ({self.end_x}, {self.end_y})"

    @staticmethod
    def from_hex_code(start_x, start_y, hex_code):
        hex_code = hex_code[1:-1][1:]
        dig_distance = int(hex_code[:-1], 16)
        dig_direction_hex = hex_code[-1]
        dig_direction = TrenchLine.hex_to_directions[
            dig_direction_hex
        ] 
        return TrenchLine(start_x, start_y, dig_direction, dig_distance)

class Lagoon:
    DEPTH = 1

    @staticmethod
    def get_trench_volume(trench_lines):
        return sum(line.distance for line in trench_lines) * Lagoon.DEPTH

    @staticmethod
    def get_lagoon_volume(trench_lines):
        polygon_points = [(line.start_x, line.start_y) for line in trench_lines] + [
            (trench_lines[-1].end_x, trench_lines[-1].end_y)
        ]
        lagoon_area = abs(
            sum(
                polygon_points[i][0] * polygon_points[(i + 1) % len(polygon_points)][1]
                - polygon_points[(i + 1) % len(polygon_points)][0]
                * polygon_points[i][1]
                for i in range(len(polygon_points))
            )
            / 2.0
        )
        num_lagoon_tiles = lagoon_area - Lagoon.get_trench_volume(trench_lines) // 2 + 1
        return int(
            (num_lagoon_tiles + Lagoon.get_trench_volume(trench_lines)) * Lagoon.DEPTH
        )

def dig_trench_simple(dig_plan):
    start_x, start_y = 0, 0
    trench_lines = []

    for direction_key, distance, _ in dig_plan:
        direction = dig_plan_to_direction[
            direction_key
        ] 
        trench_line = TrenchLine(start_x, start_y, direction, int(distance))
        trench_lines.append(trench_line)
        start_x, start_y = trench_line.end_x, trench_line.end_y

    return trench_lines

def dig_trench_hex(dig_plan):
    start_x, start_y = 0, 0
    trench_lines = []

    for _, _, hex_code in dig_plan:
        trench_line = TrenchLine.from_hex_code(start_x, start_y, hex_code)
        trench_lines.append(trench_line)
        start_x, start_y = trench_line.end_x, trench_line.end_y

    return trench_lines

def test_lavaduct_lagoon():
    input_str = """R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)"""

    dig_plan = [line.split() for line in input_str.split("\n")]
    trench_lines = dig_trench_simple(dig_plan)
    trench_volume = Lagoon.get_trench_volume(trench_lines)
    assert trench_volume == 38, f"Expected 38, got {trench_volume}"
    print("✅ get_trench_volume passed")

    lagoon_volume = Lagoon.get_lagoon_volume(trench_lines)
    assert lagoon_volume == 62, f"Expected 62, got {lagoon_volume}"
    print("✅ get_lagoon_volume passed")

def test_lavaduct_lagoon_hex():
    input_str = """R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)"""

    dig_plan = [line.split() for line in input_str.split("\n")]
    trench_lines = dig_trench_hex(dig_plan)
    trench_volume = Lagoon.get_trench_volume(trench_lines)
    assert trench_volume == 6405262, f"Expected 6405262, got {trench_volume}"
    print("✅ get_trench_volume passed")

    lagoon_volume = Lagoon.get_lagoon_volume(trench_lines)
    assert lagoon_volume == 952408144115, f"Expected 952408144115, got {lagoon_volume}"
    print("✅ get_lagoon_volume passed")

def part_one():
    with open("day_18/input.txt", "r") as f:
        dig_plan = [line.split() for line in f.readlines()]

    trench_lines = dig_trench_simple(dig_plan)
    lagoon_volume = Lagoon.get_lagoon_volume(trench_lines)
    print(f"❗️ Our lagoon can hold {lagoon_volume} m^3 of lava!")

def part_two():
    with open("day_18/input.txt", "r") as f:
        dig_plan = [line.split() for line in f.readlines()]

    trench_lines = dig_trench_hex(dig_plan)
    lagoon_volume = Lagoon.get_lagoon_volume(trench_lines)
    print(f"❗️❗️ Our lagoon can actually hold {lagoon_volume} m^3 of lava!")

if __name__ == "__main__":
    test_lavaduct_lagoon()
    test_lavaduct_lagoon_hex()
    part_one()
    part_two()

p.s: I am deliberating not adding answers here so you can actually spend some time trying to understand how we solved the problem!

Feel free to checkout code on my Github repo and try to set it up and run on your machine 👇

GitHub - jatinkrmalik/advent-of-code-2023: Well, here’s my attempt to solve all 25 problems of…
Well, here’s my attempt to solve all 25 problems of Advent of Code 2023 with Python! 🎄 - GitHub …github.com

Leave a comment

Keep reading

advent-of-code 2 min

🎄Solve Advent of Code 2024 With Me!

Announcing a spoiler-free YouTube video series for Advent of Code 2024 that focuses on how to analyze and approach each puzzle, not just the answers.