Skip to content

Commit d3d5bab

Browse files
committed
Everybody Codes: tidy 2025/06
1 parent 4f35cb1 commit d3d5bab

2 files changed

Lines changed: 55 additions & 75 deletions

File tree

Lines changed: 25 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,46 @@
11
"""Everyone Codes Day N."""
22

33
import collections
4-
import logging
5-
import itertools
64
import math
7-
from lib import parsers
5+
import time
86

9-
log = logging.info
107

118
def solve(part: int, data: str) -> int:
129
"""Solve the parts."""
13-
total = 0
1410
if part in [1, 2]:
15-
counts = collections.defaultdict(int)
11+
total = 0
12+
counts: dict[str, int] = collections.defaultdict(int)
1613
for i in data:
1714
if i.isupper():
1815
counts[i.lower()] += 1
1916
else:
20-
if (i == "a" and part == 1) or part == 2:
17+
if i == "a" or part == 2:
2118
total += counts[i]
2219
return total
2320

24-
l = len(data)
25-
window = collections.deque()
26-
mentors = collections.defaultdict(int)
27-
for i in range(1000):
28-
j = data[i % l]
29-
window.append(j)
30-
if j.isupper():
31-
mentors[j.lower()] += 1
32-
33-
limit = 1000 * (l - 1)
34-
for idx, n in enumerate(j for i in range(1000) for j in data):
35-
if idx > 1000:
36-
j = window.popleft()
37-
if j.isupper():
38-
mentors[j.lower()] -= 1
39-
j = data[(idx + 1000) % l] if idx < limit else " "
40-
window.append(j)
41-
if j.isupper():
42-
mentors[j.lower()] += 1
43-
if n.islower():
44-
total += mentors[n]
45-
46-
return total
47-
48-
49-
50-
51-
once = math.ceil(1000 / len(data))
21+
# Part three.
22+
# Number of repeated needed to get to 1000 characters.
23+
once = math.ceil(1000 / len(data))
5224
twice = once * 2
53-
three = once * 3
25+
repeats = 1000 - twice
5426

55-
mentors = collections.defaultdict(set)
56-
for idx, m in enumerate(j for i in range(twice) for j in data):
57-
if m.isupper():
58-
mentors[m.lower()].add(idx)
59-
60-
edges = 0
61-
for idx, m in enumerate(j for i in range(twice) for j in data):
62-
if m.islower():
63-
for i in mentors[m.lower()]:
64-
if abs(idx - i) <= 1000:
65-
edges += 1
66-
67-
mentors = collections.defaultdict(set)
68-
for idx, m in enumerate(j for i in range(three) for j in data):
69-
if m.isupper():
70-
mentors[m.lower()].add(idx)
71-
72-
middle = 0
73-
count = 1000 - twice
27+
total = 0
28+
# Repeat the data enough to have a look behind and look ahead of 1000.
29+
# Compute the middle portion for one repetition then multiple it.
30+
d = data * (twice + 1)
31+
shift = len(data) * once
7432
for idx, m in enumerate(data):
7533
if m.islower():
76-
for i in mentors[m.lower()]:
77-
if abs(idx + once * len(data) - i) <= 1000:
78-
middle += 1
34+
total += d[shift + idx - 1000:shift + idx + 1001].count(m.upper())
35+
total *= repeats
7936

80-
return edges + middle * count
37+
# Handle the edges where there is not a full 1000 ahead or behind.
38+
d = data * twice
39+
for idx, m in enumerate(j for i in range(twice) for j in data):
40+
if m.islower():
41+
total += d[max(idx - 1000, 0):idx + 1001].count(m.upper())
8142

43+
return total
8244

8345

8446
TESTS = [
@@ -94,4 +56,7 @@ def solve(part: int, data: str) -> int:
9456
day = __file__.split("_", maxsplit=-1)[-1].split(".")[0]
9557
for _part in range(1, 4):
9658
with open(f"inputs/{day}.{_part}.txt", encoding="utf-8") as f:
97-
print(_part, solve(_part, (f.read())))
59+
start = time.perf_counter_ns()
60+
got = solve(_part, (f.read()))
61+
end = time.perf_counter_ns()
62+
print(_part, got, (end - start) / 1_000_000_000)

pylib/running.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,22 @@ def input_data(self, part: int) -> str | None:
8989
return None
9090
return data_path.read_text().rstrip()
9191

92+
def compare(self, want, data: str, parser: typing.Callable, msg_success: str, msg_fail: str, func: typing.Callable, **kwargs):
93+
if parser:
94+
data = parser(data)
95+
time_s, got = helpers.timed(func, data=data, **kwargs)
96+
if str(got) == str(want):
97+
print(msg_success % time_s)
98+
else:
99+
msg = msg_fail % (time_s, got)
100+
if str(want).isdigit() and str(got).isdigit():
101+
delta = int(want) - int(got)
102+
if delta > 0:
103+
msg += f" Too low by {delta}."
104+
else:
105+
msg += f" Too high by {-delta}."
106+
print(msg)
107+
92108
def run_day(self, check: bool, solve: bool, test: bool, formatter) -> None:
93109
solution_file = pathlib.Path(f"{self.year}/{self.module_name()}.py")
94110
if not solution_file.exists():
@@ -100,15 +116,15 @@ def run_day(self, check: bool, solve: bool, test: bool, formatter) -> None:
100116
for part in self.parts:
101117
formatter.set_part(part)
102118
for test_number, (test_part, test_data, test_want) in enumerate(module.TESTS, 1):
103-
if parser:
104-
test_data = parser(test_data)
105119
if test_part != part:
106120
continue
107-
time_s, got = helpers.timed(module.solve, part=part, data=test_data, testing=True, test_number=test_number)
108-
if got == test_want:
109-
print(f"TEST {self.day:02}.{part} {time_s} PASS (test {test_number})")
110-
else:
111-
print(f"TEST {self.day:02}.{part} {time_s} FAIL (test {test_number}). Got {got} but wants {test_want}.")
121+
self.compare(
122+
test_want, test_data, parser,
123+
f"TEST {self.day:02}.{part} %s PASS (test {test_number})",
124+
f"TEST {self.day:02}.{part} %s FAIL (test {test_number}). Got %r but wants {test_want!r}.",
125+
module.solve,
126+
part=part, testing=True, test_number=test_number,
127+
)
112128
if solve:
113129
for part in self.parts:
114130
formatter.set_part(part)
@@ -131,14 +147,13 @@ def run_day(self, check: bool, solve: bool, test: bool, formatter) -> None:
131147
if data is None:
132148
print(f"CHECK No input data found for day {day} part {part}")
133149
continue
134-
if parser:
135-
data = parser(data)
136-
time_s, got = helpers.timed(module.solve, part=part, data=data, testing=False, test_number=None)
137-
if str(got) == want[part - 1]:
138-
print(f"CHECK {self.day:02}.{part} {time_s} PASS")
139-
else:
140-
print(f"CHECK {self.day:02}.{part} {time_s} FAIL. Wanted {want[part -1]} but got {got}.")
141-
150+
self.compare(
151+
want[part - 1], data, parser,
152+
f"CHECK {self.day:02}.{part} %s PASS",
153+
f"CHECK {self.day:02}.{part} %s FAIL. Wanted {want[part -1]} but got %s.",
154+
module.solve,
155+
part=part, testing=False, test_number=None,
156+
)
142157

143158
def run(self, check: bool, solve: bool, test: bool, live: bool) -> None:
144159
formatter = helpers.setup_logging(self.day, self.verbose)

0 commit comments

Comments
 (0)