Learning Python with Advent of Code Walkthroughs

Dazbo's Advent of Code solutions, written in Python

Advent of Code 2015 - Day 2

Problem Intro

The elves need more wrapping paper.

We’re given the dimensions (length, width, height) of a list of presents, where dimensions are given in feet. E.g.

``````2x3x4
1x1x10
``````

Every present is a rectangular cuboid, so we know the surface area is given by `2(lw + wh + lh)`. That’s because the cuboid is composed of three pairs of opposite faces. So we work out the area of each of three different faces, add them together, and then multiply by two.

Part 1

The elves need enough paper to wrap all the presents. They require some extra paper for contingency: the area of the smallest side of each present.

How many total square feet of wrapper paper should they order?

This is easy enough to work out. We need to calculate the total surface area of each present, which we already know the formula for. And then we need to add the surface area of the smallest side. This will be determined by the product of the two shortest sides.

Setup

``````from dataclasses import dataclass
import os
import time
import re

SCRIPT_DIR = os.path.dirname(__file__)
INPUT_FILE = "input/input.txt"
SAMPLE_INPUT_FILE = "input/sample_input.txt"
``````

The only thing worth mentioning here is two imports, which we didn’t use in Day 1:

• dataclass - for special kind of classes that saves a lot of repetitive typing!
• re - for working with regular expressions

The Box

First, let’s create a class to represent our present:

``````@dataclass
class Box():
""" Cuboid """
width: int
height: int
length: int

def __init__(self, dims: list) -> None:
sorted_dims = sorted(dims)
self.width = sorted_dims[0]
self.height = sorted_dims[1]
self.length = sorted_dims[2]

@property
def area(self):
return 2*(self.width*self.height + self.width*self.length + self.height*self.length)

@property
def contingency(self):
""" Contigency is the same as the area of the smallest face """
return self.width * self.height
``````
• This `Box` has three key instance variables: the `width`, `height`, and `length` of the box.
• It has an `__init__()` method which creates an instance of the `Box` class.
• This method expects that we pass it a `list` that contains the lengths of our three sides.
• Note: the order of the sides in this `list` doesn’t matter. That’s because we always sort the sides from shortest to longest, when we initialise our `Box`. We do this using the `sorted()` method.
• Arbitrarily, I’ve decided that `width` will always be the shortest side, and `length` will always be the longest side.
• We expose the surface area of the box, using a property called `area`.
• We expose the extra contingency area through a property called `contingency`. This calculates the area of the smallest face, by multiplying the `width` with the `height`. We know these will always be the shortest of the three sides.

Solving the Problem

Now we’re ready to solve the problem!

``````def main():
# input_file = os.path.join(SCRIPT_DIR, SAMPLE_INPUT_FILE)
input_file = os.path.join(SCRIPT_DIR, INPUT_FILE)
with open(input_file, mode="rt") as f:

boxes = get_boxes(data)

paper = sum(paper_required(box) for box in boxes)
print(f"Paper required: {paper}")

def paper_required(box: Box):
return box.area + box.contingency

def get_boxes(data) -> list[Box]:
boxes = []

p = re.compile(r"(\d+)x(\d+)x(\d+)")
for line in data:
if match := p.match(line):
dims = list(map(int, match.groups())) # dims as list of int
boxes.append(Box(dims))

return boxes
``````
• First, our `main()` function reads all the lines of the input. Each line contains our three dimensions, e.g. `2x3x4`. The result is a `list` of strings, which we call `boxes`.
• We then pass this `list` to our `get_boxes()` function. This:
• Initialises an empty `list`, called `boxes`.
• Creates a regex pattern, called `p`. This pattern is looking for three separate groups of digits. Each group is given by `(\d+)`, meaning that the group must contain a numeric digit, and must be at least one character long.
• We then iterate through each line in the `list`. We apply our regex pattern against each line. Each line should return a successful `match` object.
• When we call the `groups()` method on the `match` object, we know it will return a tuple with three items. However, these will all be returned as `str` objects. So we want to convert them integers using the `map()` function. This function works by taking every member of an iterable (like a `list`), and applying a particular function to each member. So here, we’re applying the `int()` function against the three strings in our `match.groups()` tuple. Thus, the resulting `dims` is a `list` of `int` values, i.e. three numbers representing our three box lengths.
• We then create a `Box` object from our `dims`.
• We’ve created a function called `paper_required()`. This function takes a `Box`, and returns the sum of the surface area of the box, and the contingency area of the box.
• Finally, we use a list comprehension to apply our `paper_required()` function to each `Box` in our `list` of boxes. We use `sum()` to add up all the results. And this gives us our answer!!

Part 2

We’re told we also need to order ribbon. The amount of ribbon required is given by:

• the shortest perimeter around the cuboid, plus
• a length that is the same number as the volume of the cuboid.

How much ribbon do we need to order?

Solving the Problem

We don’t need to do much here. We just need to add a new function that:

• Calculates the shortest perimeter of the cuboid. Since we’ve already ensured that `width` and `height` are the two shortest sides, then our perimeter will simply be `2*(width+height)`.
• Calculate the volume of the cuboid. This will simply be `width*height*length`.
• Add the two numbers together.

Our new function looks like this:

``````def ribbon_required(box: Box):
ribbon_length = 2*box.width + 2*box.height
bow_length = box.volume # we're told box length will be the same as the volume

return ribbon_length + bow_length
``````

And we call it using a `list comprehension`, just as we did before:

``````    ribbon = sum(ribbon_required(box) for box in boxes)
print(f"Ribbon required: {ribbon}")
``````

Output:

``````Paper required: 1606483
Ribbon required: 3842356
Execution time: 0.0030 seconds
``````