Source: Transparent Origami
Part 1: Given a set of points and a sequence of ‘fold’ lines (where you either fold the bottom over the top or right over left), determine how many points exist after the first fold.
First, data structures:
@dataclass(frozen=True)
class Point:
x: int
y: int
def __repr__(self):
return f'<{self.x}, {self.y}>'
@dataclass(frozen=True)
class Fold:
horizontal: bool
line: int
def apply_to(self, points: Set[Point]) -> Set[Point]:
return {
Point(
p.x if (self.horizontal or p.x < self.line) else self.line * 2 - p.x,
p.y if (not self.horizontal or p.y < self.line) else self.line * 2 - p.y
)
for p in points
}
def __repr__(self):
return f'fold@{"x" if self.horizontal else "y"}={self.line}'
Yes. Those could (mostly) be tuples. But I happen to like putting them in their own classes. The apply_to
method is actually the bulk of the functionality here.
What you need to consider is when the x
/y
of a point do not change. If you’re folding a horizontal line, the x
will never change (likewise for vertical and y
). Also, if you’re in the top or left half, you’ll not change.
There’s only 1 in 4 cases that change: if you’re below/right of the fold that’s changing.
In that case, you are going to reflect it. In this case, if you’re folding a point with y=14
over the line y=7
, then you should end up with 1
. Thus line*2-y
.
Next, two helper functions in order to load/print the current points:
def load(file: TextIO) -> Tuple[Set[Point], List[Fold]]:
points = set()
folds = []
for line in file:
if ',' in line:
xs, ys = line.split(',')
points.add(Point(x=int(xs), y=int(ys)))
elif '=' in line:
left, vs = line.split('=')
folds.append(Fold(horizontal=left.endswith('y'), line=int(vs)))
return points, folds
def render(points: Set[Point]):
width = max(p.x + 1 for p in points)
height = max(p.y + 1 for p in points)
print('\n'.join(
''.join(
'*' if Point(x, y) in points else ' '
for x in range(width)
)
for y in range(height)
))
print()
Straight forward enough. Technically, this is a more flexible format than given (you can mix points and folds in any order), but that’s a superset of what we’re actually interested in, so good enough.
Now that we have all of that, we can actually do all of the folds… but we only need the first:
def part1(file: typer.FileText):
points, folds = load(file)
points = folds[0].apply_to(points)
print(len(points))
So short. 😄
$ python3 foldinator.py part1 input.txt
706
Heh.
Part 2: After all defined folds, what letters are revealed?
We’ve already done all of this while testing part 1, so on we go!
@app.command()
def part2(file: typer.FileText):
points, folds = load(file)
for fold in folds:
points = fold.apply_to(points)
render(points)
That seems like cheating, but does it work?
$ python3 foldinator.py part2 input.txt
* *** **** ** *** ** **** * *
* * * * * * * * * * *
* * * *** * *** * *** ****
* *** * * * * * * * *
* * * * * * * * * * * * *
**** * * * ** *** ** **** * *
Fun!
I could write something to turn those into an actual string, but … nah, not right now.
That’s it for today. Fun one!