Add visualisation for day 4
This commit is contained in:
parent
846f8e73c2
commit
b661de71ed
7 changed files with 139 additions and 4 deletions
|
@ -2,4 +2,10 @@
|
|||
|
||||
Part two is:
|
||||
* less than 1976
|
||||
* also, somewhat obviously, less than 1978
|
||||
* also, somewhat obviously, less than 1978
|
||||
|
||||
Heatmap of the most overlapped locations
|
||||
|
||||
| Part 1 | Part 2 |
|
||||
|--------|--------|
|
||||
|  |  |
|
5
challenges/2024/04-ceresSearch/generate-vis.sh
Normal file
5
challenges/2024/04-ceresSearch/generate-vis.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
cat input.txt | PYTHONPATH=../../../ python3 vis.py
|
BIN
challenges/2024/04-ceresSearch/heatmap-1.png
Normal file
BIN
challenges/2024/04-ceresSearch/heatmap-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
challenges/2024/04-ceresSearch/heatmap-2.png
Normal file
BIN
challenges/2024/04-ceresSearch/heatmap-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
122
challenges/2024/04-ceresSearch/vis.py
Normal file
122
challenges/2024/04-ceresSearch/vis.py
Normal file
|
@ -0,0 +1,122 @@
|
|||
import sys
|
||||
import gridutil.grid as grid
|
||||
import gridutil.coord as coord
|
||||
from collections import defaultdict
|
||||
import os
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
def parse(instr: str) -> grid.Grid:
|
||||
return grid.parse(instr.upper())
|
||||
|
||||
def one(instr: str):
|
||||
wordsearch = parse(instr)
|
||||
|
||||
seq_starts = list(
|
||||
map(lambda x: x[0], filter(lambda x: x[1] == "X", wordsearch.items()))
|
||||
)
|
||||
detected_sequences = set()
|
||||
|
||||
for start_pos in seq_starts:
|
||||
for xdir in [-1, 0, 1]:
|
||||
for ydir in [-1, 0, 1]:
|
||||
|
||||
if xdir == 0 and ydir == 0:
|
||||
continue
|
||||
|
||||
delta = coord.Coordinate(xdir, ydir)
|
||||
|
||||
ok = True
|
||||
b = []
|
||||
for i, v in enumerate("XMAS"):
|
||||
if not ok:
|
||||
break
|
||||
|
||||
x = coord.add(start_pos, coord.mult(delta, i))
|
||||
g = wordsearch.get(x, "-")
|
||||
ok = g == v
|
||||
b.append(x)
|
||||
|
||||
if ok:
|
||||
detected_sequences.add(tuple(b))
|
||||
|
||||
return detected_sequences
|
||||
|
||||
|
||||
def check_cross_adjacents(s: str) -> bool:
|
||||
return s == "SM" or s == "MS"
|
||||
|
||||
|
||||
def two(instr: str):
|
||||
wordsearch = parse(instr)
|
||||
|
||||
seq_starts = list(
|
||||
map(lambda x: x[0], filter(lambda x: x[1] == "A", wordsearch.items()))
|
||||
)
|
||||
detected_sequences = set()
|
||||
|
||||
for start_pos in seq_starts:
|
||||
|
||||
a = wordsearch.get(coord.add(start_pos, (-1, -1)), "") + wordsearch.get(
|
||||
coord.add(start_pos, (1, 1)), ""
|
||||
)
|
||||
b = wordsearch.get(coord.add(start_pos, (-1, 1)), "") + wordsearch.get(
|
||||
coord.add(start_pos, (1, -1)), ""
|
||||
)
|
||||
|
||||
if check_cross_adjacents(a) and check_cross_adjacents(b):
|
||||
detected_sequences.add(start_pos)
|
||||
|
||||
return detected_sequences
|
||||
|
||||
|
||||
lowest_colour = (255, 245, 237)
|
||||
highest_colour = (255, 159, 45)
|
||||
colour_diffs = tuple(map(lambda x: x[1] - x[0], zip(highest_colour, lowest_colour)))
|
||||
|
||||
|
||||
def get_colour_for(n):
|
||||
return tuple(
|
||||
map(int, map(lambda x: x[0] - x[1], zip(lowest_colour, map(lambda x: x * n, colour_diffs))))
|
||||
)
|
||||
|
||||
|
||||
scale_factor = 4
|
||||
|
||||
def generate_frame(path: str, wordsearch: grid.Grid, counts: dict[coord.Coordinate, int]):
|
||||
max_val = max(counts.values())
|
||||
|
||||
maxx, maxy = grid.get_max_x(wordsearch), grid.get_max_y(wordsearch)
|
||||
|
||||
img = Image.new("RGB", (maxx+1, maxy+1))
|
||||
|
||||
for x in range(maxx+1):
|
||||
for y in range(maxy+1):
|
||||
img.putpixel((x, y), get_colour_for(counts[(x, y)]/max_val))
|
||||
|
||||
img = img.resize((maxx * scale_factor, maxy * scale_factor), resample=Image.NEAREST)
|
||||
img.save(path)
|
||||
|
||||
|
||||
def main():
|
||||
inp = sys.stdin.read().strip()
|
||||
wordsearch = parse(inp)
|
||||
|
||||
j = defaultdict(lambda: 0)
|
||||
for state in one(inp):
|
||||
for s in state:
|
||||
j[s] = j[s] + 1
|
||||
generate_frame("heatmap-1.png", wordsearch, j)
|
||||
|
||||
j = defaultdict(lambda: 0)
|
||||
for state in two(inp):
|
||||
for dir in [(0, 0), (-1, -1), (-1, 1), (1, 1), (1, -1)]:
|
||||
s = coord.add(state, dir)
|
||||
j[s] = j[s] + 1
|
||||
generate_frame("heatmap-2.png", wordsearch, j)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -10,11 +10,11 @@ Total stars: **8 ★**
|
|||
|
||||
<!-- ★ ☆ ✗ -->
|
||||
|
||||
A day denoted with a star means it has a visualisation.
|
||||
A day denoted with an asterisk means it has a visualisation.
|
||||
|
||||
| Day | Status | Solutions | Notes |
|
||||
|-------------------------------------|--------|----------------------|-------|
|
||||
| 01 - Historian Hysteria | ★ ★ | Python | The reading comprehension was the hardest part of this. |
|
||||
| 02 - Red-Nosed Reindeer | ★ ★ | Python ||
|
||||
| 03 - Mull It Over | ★ ★ | Python | The first instance of Advent of Parsing this year! |
|
||||
| 04 - Ceres Search | ★ ★ | Python | When it says a cross, it does not mean a plus. |
|
||||
| 04* - Ceres Search | ★ ★ | Python | When it says a cross, it does not mean a plus. |
|
|
@ -80,4 +80,6 @@ class Direction(Enum):
|
|||
return self.value == x.value
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.value)
|
||||
return hash(self.value)
|
||||
|
||||
directions = [Direction.Up, Direction.Down, Direction.Left, Direction.Right]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue