Dazbo's Advent of Code solutions, written in Python
Day 2: I Was Told There Would Be No Math
Regular Expressions (Regex)DataclassessortedComprehensionssum
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.
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.
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:
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
Box
has three key instance variables: the width
, height
, and length
of the box.__init__()
method which creates an instance of the Box
class.
list
that contains the lengths of our three sides.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.width
will always be the shortest side, and length
will always be the longest side.area
.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.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:
data = f.readlines()
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
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
.list
to our get_boxes()
function. This:
list
, called boxes
.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.list
. We apply our regex pattern against each line. Each line should return a successful match
object.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.Box
object from our dims
.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.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!!We’re told we also need to order ribbon. The amount of ribbon required is given by:
How much ribbon do we need to order?
We don’t need to do much here. We just need to add a new function that:
width
and height
are the two shortest sides, then our perimeter will simply be 2*(width+height)
.width*height*length
.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