Dazbo's Advent of Code solutions, written in Python
EnumerateEnum (GeeksforGeeks)Enum (Real Python)Enum (Python Docs)classes
The enumerate function is useful for obtaining a counter for any loop.
Here is a demonstration of how we can use enumerate()
with a list
of input lines:
input_data = """Some line of text
Another line
The last line"""
input_lines = input_data.splitlines()
print(input_lines)
print(list(enumerate(input_lines)))
Here’s the output:
['Some line of text', 'Another line', 'The last line']
[(0, 'Some line of text'), (1, 'Another line'), (2, 'The last line')]
We can see that the enumerate()
function has turned each line into a numbered tuple
.
A more common way to use enumerate()
is in a loop. For example:
print("Iterating over names...")
names = ("Darren", "Josh", "Julie")
for name in names:
print(name)
print("\nAnd now, with enumeration...")
for i, name in enumerate(names):
print(f"{i}: {name}")
Output:
Darren
Josh
Julie
And now, with enumeration...
0: Darren
1: Josh
2: Julie
Thus, using enumerate
has given us a counter, which starts at 0 by default. We can change the start value by adding a second parameter to enumerate()
, i.e. start=some_value
. For example:
names = ("Darren", "Josh", "Julie")
for i, name in enumerate(names, start=1):
print(f"{i}: {name}")
Output:
1: Darren
2: Josh
3: Julie
We can also create an Enum data type in Python. This is quite different to using enumerate()
. Instead, an Enum data type allows us to create a set of related, named constants.
Here’s an example of we might use an Enum:
from enum import Enum
class Vector(Enum):
""" Enumeration of 8 directions, and a rotating list of direction choices. """
N = (0, -1)
E = (1, 0)
S = (0, 1)
W = (-1, 0)
print("Iterating over Vector...")
for direction in Vector:
print(direction)
print(f"{direction.name}: {direction.value}")
print("\nBy specific item...")
print(Vector.N.name)
print(Vector.N.value)
Here’s the output:
Iterating over Vector...
Vector.N
N: (0, -1)
Vector.E
E: (1, 0)
Vector.S
S: (0, 1)
Vector.W
W: (-1, 0)
By specific item...
N
(0, -1)
Some things to note:
Enum
.N
, E
, S
, and W
.AttributeError
.Vector
), its name (e.g. N
), and its value (e.g. (0, -1)
).We could create similar behaviour by using a dictionary. E.g.
VECTOR = {
"N": (0, -1),
"E": (1, 0),
"S": (0, 1),
"W": (-1, 0)
}
print("Iterating over Vector...")
for direction in VECTOR:
print(direction)
print(f"{direction}: {VECTOR[direction]}")
print("\nBy specific item...")
print(VECTOR["N"])
Output:
N
N: (0, -1)
E
E: (1, 0)
S
S: (0, 1)
W
W: (-1, 0)
By specific item...
(0, -1)
The main disadvantage of using a dict
is that it’s easy to create a typo when specifying the key.
Here’s a more interesting example:
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
@dataclass(frozen=True)
class Point():
""" Point x,y which knows how to add another point,
and how to return all adjacent points, including diagonals. """
x: int
y: int
def all_neighbours(self) -> set[Point]:
""" Return all adjacent orthogonal (not diagonal) Points """
return {(self + vector.value) for vector in list(Vector)}
def get_neighbours(self, directions: list[Vector]) -> set[Point]:
return {(self + vector.value) for vector in list(directions)}
def __add__(self, other) -> Point:
""" Subtract other point from this point, returning new point vector """
return Point(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"P({self.x},{self.y})"
class Vector(Enum):
""" Enumeration of 8 directions, and a rotating list of direction choices. """
N = Point(0, -1)
NE = Point(1, -1)
E = Point(1, 0)
SE = Point(1, 1)
S = Point(0, 1)
SW = Point(-1, 1)
W = Point(-1, 0)
NW = Point(-1, -1)
start = Point(10, 10)
print(f"All adjacent points to {start}:")
print(start.all_neighbours())
print(f"Get northerly neighbours to {start}:")
print(start.get_neighbours([Vector.NW, Vector.N, Vector.NE]))