77 lines
1.8 KiB
Python
77 lines
1.8 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
import dataclasses
|
||
|
import sys
|
||
|
from typing import Optional
|
||
|
|
||
|
|
||
|
@dataclasses.dataclass
|
||
|
class Lens:
|
||
|
label: str
|
||
|
num: int
|
||
|
|
||
|
def __eq__(self, other: object) -> bool:
|
||
|
if isinstance(other, str):
|
||
|
return self.label == other
|
||
|
return super().__eq__(other)
|
||
|
|
||
|
|
||
|
def solve(input: list[str]) -> int:
|
||
|
def compute_hash(string: str) -> int:
|
||
|
res = 0
|
||
|
|
||
|
for c in string:
|
||
|
res += ord(c)
|
||
|
res *= 17
|
||
|
res %= 256
|
||
|
|
||
|
return res
|
||
|
|
||
|
def parse_step(step: str) -> tuple[str, Optional[int]]:
|
||
|
if step[-1] == "-":
|
||
|
return step[:-1], None
|
||
|
label, num = step.split("=")
|
||
|
return label, int(num)
|
||
|
|
||
|
def find_label(label: str, box: list[Lens]) -> Optional[int]:
|
||
|
for i, lens in enumerate(box):
|
||
|
if lens.label == label:
|
||
|
return i
|
||
|
return None
|
||
|
|
||
|
def focusing_power(boxes: list[list[Lens]]) -> int:
|
||
|
res = 0
|
||
|
|
||
|
for box_num, box in enumerate(boxes, start=1):
|
||
|
for i, lens in enumerate(box, start=1):
|
||
|
res += box_num * i * lens.num
|
||
|
|
||
|
return res
|
||
|
|
||
|
boxes: list[list[Lens]] = [[] for _ in range(256)]
|
||
|
|
||
|
for label, num in map(parse_step, input):
|
||
|
box = compute_hash(label)
|
||
|
index = find_label(label, boxes[box])
|
||
|
if num is None:
|
||
|
# Remove label from box
|
||
|
if index is not None:
|
||
|
del boxes[box][index]
|
||
|
# Place len in box
|
||
|
elif index is not None:
|
||
|
boxes[box][index].num = num
|
||
|
else:
|
||
|
boxes[box].append(Lens(label, num))
|
||
|
|
||
|
return focusing_power(boxes)
|
||
|
|
||
|
|
||
|
def main() -> None:
|
||
|
input = sys.stdin.read().splitlines()
|
||
|
assert len(input) == 1 # Sanity check
|
||
|
print(solve(input[0].split(",")))
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|