Compare commits
	
		
			81 Commits
		
	
	
		
			master
			...
			2023/day12
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					155e0e9521 | ||
| 
						 | 
					13b15aba76 | ||
| 
						 | 
					c7d2eb2171 | ||
| 
						 | 
					edc50cb9c2 | ||
| 
						 | 
					7151499835 | ||
| 
						 | 
					1a6ab1cc0e | ||
| 
						 | 
					f5aabbee8f | ||
| 
						 | 
					6c00341ab0 | ||
| 
						 | 
					755e0bd4b3 | ||
| 
						 | 
					a52d077a40 | ||
| 
						 | 
					3fc0f94b1c | ||
| 
						 | 
					8a0412f926 | ||
| 
						 | 
					855efeb0aa | ||
| 
						 | 
					f2a65e03e5 | ||
| 
						 | 
					759f47bfab | ||
| 
						 | 
					999207b007 | ||
| 
						 | 
					d92e4744a4 | ||
| 
						 | 
					8dbf0f101c | ||
| 
						 | 
					b8d8df06d6 | ||
| 
						 | 
					825ebea299 | ||
| 
						 | 
					869cd4477f | ||
| 
						 | 
					fd777627d6 | ||
| 
						 | 
					98f605f30e | ||
| 
						 | 
					d51fed283c | ||
| 
						 | 
					e991cd8b04 | ||
| 
						 | 
					10f67e6bfd | ||
| 
						 | 
					f291b0aa3f | ||
| 
						 | 
					0eb5b5a88f | ||
| 
						 | 
					2ec0a3d5f9 | ||
| 
						 | 
					0327a3f36a | ||
| 
						 | 
					3732e70ef7 | ||
| 
						 | 
					b0cc6b4a46 | ||
| 
						 | 
					8c24b9f9e2 | ||
| 
						 | 
					dca6f6a08f | ||
| 
						 | 
					8d7a20f575 | ||
| 
						 | 
					3934dfd152 | ||
| 
						 | 
					b656e8929e | ||
| 
						 | 
					c9c69f479b | ||
| 
						 | 
					72ebcfff1f | ||
| 
						 | 
					dd72bb3238 | ||
| 
						 | 
					c1dd74c57d | ||
| 
						 | 
					1bf2de62c7 | ||
| 
						 | 
					df808bc98a | ||
| 
						 | 
					f46e190e98 | ||
| 
						 | 
					7f4a34b2d7 | ||
| 
						 | 
					ddebd26db2 | ||
| 
						 | 
					01300e23b2 | ||
| 
						 | 
					b8e2faa8c9 | ||
| 
						 | 
					ea5b757180 | ||
| 
						 | 
					89a71c175f | ||
| 
						 | 
					9ffb332dea | ||
| 
						 | 
					8167ab34c7 | ||
| 
						 | 
					100df02a09 | ||
| 
						 | 
					15b987a590 | ||
| 
						 | 
					b1578f5709 | ||
| 
						 | 
					d80dbb6c7c | ||
| 
						 | 
					b679c1f895 | ||
| 
						 | 
					e9d5f9747b | ||
| 
						 | 
					fe3aad7ddd | ||
| 
						 | 
					7ac9981ae5 | ||
| 
						 | 
					652756a341 | ||
| 
						 | 
					c7ef505f1b | ||
| 
						 | 
					c55f6ac8e1 | ||
| 
						 | 
					726a6aecac | ||
| 
						 | 
					291b188238 | ||
| 
						 | 
					289e3b7d02 | ||
| 
						 | 
					9820765e9c | ||
| 
						 | 
					c6522de8a2 | ||
| 
						 | 
					80465e5e53 | ||
| 
						 | 
					af1428b5e1 | ||
| 
						 | 
					fca283527d | ||
| 
						 | 
					0d37458ec5 | ||
| 
						 | 
					198927e4a3 | ||
| 
						 | 
					4192c98bba | ||
| 
						 | 
					7cb8317659 | ||
| 
						 | 
					f46cb51c60 | ||
| 
						 | 
					261a396ae7 | ||
| 
						 | 
					4b3af377ab | ||
| 
						 | 
					f697415ef2 | ||
| 
						 | 
					ac2806b0fb | ||
| 
						 | 
					c62b8abfd0 | 
							
								
								
									
										12
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -1,12 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
name: default
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - name: tests
 | 
			
		||||
    image: python:3.10-slim
 | 
			
		||||
    commands:
 | 
			
		||||
      - pip install poetry
 | 
			
		||||
      - poetry install
 | 
			
		||||
      - poetry run poe lint
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1 @@
 | 
			
		||||
# python / VS Code
 | 
			
		||||
venv
 | 
			
		||||
__pycache__
 | 
			
		||||
.ruff_cache
 | 
			
		||||
.vscode
 | 
			
		||||
build
 | 
			
		||||
files
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								2021/day1.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								2021/day1.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
values = [int(line) for line in lines]
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = sum(v2 > v1 for v1, v2 in zip(values[:-1], values[1:]))
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
runnings = [sum(values[i : i + 3]) for i in range(len(values) - 2)]
 | 
			
		||||
answer_2 = sum(v2 > v1 for v1, v2 in zip(runnings[:-1], runnings[1:]))
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day10.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day10.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day11.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day11.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day12.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day12.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day13.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day13.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day14.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day14.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day15.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day15.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day16.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day16.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day17.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day17.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day18.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day18.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day19.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day19.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										40
									
								
								2021/day2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								2021/day2.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import sys
 | 
			
		||||
from math import prod
 | 
			
		||||
from typing import Literal, cast
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
commands = [
 | 
			
		||||
    (cast(Literal["forward", "up", "down"], (p := line.split())[0]), int(p[1]))
 | 
			
		||||
    for line in lines
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def depth_and_position(use_aim: bool):
 | 
			
		||||
    aim, pos, depth = 0, 0, 0
 | 
			
		||||
    for command, value in commands:
 | 
			
		||||
        d_depth = 0
 | 
			
		||||
        match command:
 | 
			
		||||
            case "forward":
 | 
			
		||||
                pos += value
 | 
			
		||||
                depth += value * aim
 | 
			
		||||
            case "up":
 | 
			
		||||
                d_depth = -value
 | 
			
		||||
            case "down":
 | 
			
		||||
                d_depth = value
 | 
			
		||||
 | 
			
		||||
        if use_aim:
 | 
			
		||||
            aim += d_depth
 | 
			
		||||
        else:
 | 
			
		||||
            depth += value
 | 
			
		||||
 | 
			
		||||
    return depth, pos
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = prod(depth_and_position(False))
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = prod(depth_and_position(True))
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day20.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day20.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day21.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day21.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day22.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day22.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day23.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day23.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day24.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day24.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day25.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day25.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										39
									
								
								2021/day3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								2021/day3.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import Counter
 | 
			
		||||
from typing import Literal
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generator_rating(
 | 
			
		||||
    values: list[str], most_common: bool, default: Literal["0", "1"]
 | 
			
		||||
) -> str:
 | 
			
		||||
    index = 0
 | 
			
		||||
    most_common_idx = 0 if most_common else 1
 | 
			
		||||
 | 
			
		||||
    while len(values) > 1:
 | 
			
		||||
        cnt = Counter(value[index] for value in values)
 | 
			
		||||
        bit = cnt.most_common(2)[most_common_idx][0]
 | 
			
		||||
        if cnt["0"] == cnt["1"]:
 | 
			
		||||
            bit = default
 | 
			
		||||
        values = [value for value in values if value[index] == bit]
 | 
			
		||||
        index += 1
 | 
			
		||||
 | 
			
		||||
    return values[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
most_and_least_common = [
 | 
			
		||||
    tuple(Counter(line[col] for line in lines).most_common(2)[m][0] for m in range(2))
 | 
			
		||||
    for col in range(len(lines[0]))
 | 
			
		||||
]
 | 
			
		||||
gamma_rate = int("".join(most for most, _ in most_and_least_common), base=2)
 | 
			
		||||
epsilon_rate = int("".join(least for _, least in most_and_least_common), base=2)
 | 
			
		||||
print(f"answer 1 is {gamma_rate * epsilon_rate}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
 | 
			
		||||
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
 | 
			
		||||
answer_2 = oxygen_generator_rating * co2_scrubber_rating
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										45
									
								
								2021/day4.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								2021/day4.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
numbers = [int(c) for c in lines[0].split(",")]
 | 
			
		||||
 | 
			
		||||
boards = np.asarray(
 | 
			
		||||
    [
 | 
			
		||||
        [[int(c) for c in line.split()] for line in lines[start : start + 5]]
 | 
			
		||||
        for start in range(2, len(lines), 6)
 | 
			
		||||
    ]
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# (round, score) for each board (-1 when not found)
 | 
			
		||||
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
 | 
			
		||||
marked = np.zeros_like(boards, dtype=bool)
 | 
			
		||||
 | 
			
		||||
for round, number in enumerate(numbers):
 | 
			
		||||
    # mark boards
 | 
			
		||||
    marked[boards == number] = True
 | 
			
		||||
 | 
			
		||||
    # check each board for winning
 | 
			
		||||
    for index in range(len(boards)):
 | 
			
		||||
        if winning_rounds[index][0] > 0:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        if np.any(np.all(marked[index], axis=0) | np.all(marked[index], axis=1)):
 | 
			
		||||
            winning_rounds[index] = (
 | 
			
		||||
                round,
 | 
			
		||||
                number * int(np.sum(boards[index][~marked[index]])),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    # all boards are winning - break
 | 
			
		||||
    if np.all(marked.all(axis=1) | marked.all(axis=2)):
 | 
			
		||||
        break
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
(_, score) = min(winning_rounds, key=lambda w: w[0])
 | 
			
		||||
print(f"answer 1 is {score}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
(_, score) = max(winning_rounds, key=lambda w: w[0])
 | 
			
		||||
print(f"answer 2 is {score}")
 | 
			
		||||
							
								
								
									
										48
									
								
								2021/day5.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								2021/day5.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
lines: list[str] = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
 | 
			
		||||
    (
 | 
			
		||||
        (
 | 
			
		||||
            int(line.split(" -> ")[0].split(",")[0]),
 | 
			
		||||
            int(line.split(" -> ")[0].split(",")[1]),
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            int(line.split(" -> ")[1].split(",")[0]),
 | 
			
		||||
            int(line.split(" -> ")[1].split(",")[1]),
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    for line in lines
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
np_sections = np.array(sections).reshape(-1, 4)
 | 
			
		||||
 | 
			
		||||
x_min, x_max, y_min, y_max = (
 | 
			
		||||
    min(np_sections[:, 0].min(), np_sections[:, 2].min()),
 | 
			
		||||
    max(np_sections[:, 0].max(), np_sections[:, 2].max()),
 | 
			
		||||
    min(np_sections[:, 1].min(), np_sections[:, 3].min()),
 | 
			
		||||
    max(np_sections[:, 1].max(), np_sections[:, 3].max()),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
 | 
			
		||||
counts_2 = counts_1.copy()
 | 
			
		||||
 | 
			
		||||
for (x1, y1), (x2, y2) in sections:
 | 
			
		||||
    x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1)
 | 
			
		||||
    y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1)
 | 
			
		||||
 | 
			
		||||
    if x1 == x2 or y1 == y2:
 | 
			
		||||
        counts_1[list(y_rng), list(x_rng)] += 1
 | 
			
		||||
        counts_2[list(y_rng), list(x_rng)] += 1
 | 
			
		||||
    elif abs(x2 - x1) == abs(y2 - y1):
 | 
			
		||||
        for i, j in zip(y_rng, x_rng):
 | 
			
		||||
            counts_2[i, j] += 1
 | 
			
		||||
 | 
			
		||||
answer_1 = (counts_1 >= 2).sum()
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
answer_2 = (counts_2 >= 2).sum()
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										21
									
								
								2021/day6.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								2021/day6.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
values = [int(c) for c in sys.stdin.read().strip().split(",")]
 | 
			
		||||
 | 
			
		||||
days = 256
 | 
			
		||||
lanterns = {day: 0 for day in range(days)}
 | 
			
		||||
for value in values:
 | 
			
		||||
    for day in range(value, days, 7):
 | 
			
		||||
        lanterns[day] += 1
 | 
			
		||||
 | 
			
		||||
for day in range(days):
 | 
			
		||||
    for day2 in range(day + 9, days, 7):
 | 
			
		||||
        lanterns[day2] += lanterns[day]
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = sum(v for k, v in lanterns.items() if k < 80) + len(values)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = sum(lanterns.values()) + len(values)
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										21
									
								
								2021/day7.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								2021/day7.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
positions = np.asarray([int(c) for c in sys.stdin.read().strip().split(",")])
 | 
			
		||||
 | 
			
		||||
min_position, max_position = positions.min(), positions.max()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = min(
 | 
			
		||||
    np.sum(np.abs(positions - position))
 | 
			
		||||
    for position in range(min_position, max_position + 1)
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = min(
 | 
			
		||||
    np.sum(abs(positions - position) * (abs(positions - position) + 1) // 2)
 | 
			
		||||
    for position in range(min_position, max_position + 1)
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										87
									
								
								2021/day8.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								2021/day8.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
import itertools
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
 | 
			
		||||
 | 
			
		||||
digits = {
 | 
			
		||||
    "abcefg": 0,
 | 
			
		||||
    "cf": 1,
 | 
			
		||||
    "acdeg": 2,
 | 
			
		||||
    "acdfg": 3,
 | 
			
		||||
    "bcdf": 4,
 | 
			
		||||
    "abdfg": 5,
 | 
			
		||||
    "abdefg": 6,
 | 
			
		||||
    "acf": 7,
 | 
			
		||||
    "abcdefg": 8,
 | 
			
		||||
    "abcdfg": 9,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
 | 
			
		||||
answer_1 = sum(
 | 
			
		||||
    len(p) in lengths for line in lines for p in line.split("|")[1].strip().split()
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
values: list[int] = []
 | 
			
		||||
 | 
			
		||||
for line in lines:
 | 
			
		||||
    parts = line.split("|")
 | 
			
		||||
    broken_digits = sorted(parts[0].strip().split(), key=len)
 | 
			
		||||
 | 
			
		||||
    per_length = {
 | 
			
		||||
        k: list(v)
 | 
			
		||||
        for k, v in itertools.groupby(sorted(broken_digits, key=len), key=len)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # a can be found immediately
 | 
			
		||||
    a = next(u for u in per_length[3][0] if u not in per_length[2][0])
 | 
			
		||||
 | 
			
		||||
    # c and f have only two possible values corresponding to the single entry of
 | 
			
		||||
    # length 2
 | 
			
		||||
    cf = list(per_length[2][0])
 | 
			
		||||
 | 
			
		||||
    # the only digit of length 4 contains bcdf, so we can deduce bd by removing cf
 | 
			
		||||
    bd = [u for u in per_length[4][0] if u not in cf]
 | 
			
		||||
 | 
			
		||||
    # the 3 digits of length 5 have a, d and g in common
 | 
			
		||||
    adg = [u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])]
 | 
			
		||||
 | 
			
		||||
    # we can remove a
 | 
			
		||||
    dg = [u for u in adg if u != a]
 | 
			
		||||
 | 
			
		||||
    # we can deduce d and g
 | 
			
		||||
    d = next(u for u in dg if u in bd)
 | 
			
		||||
    g = next(u for u in dg if u != d)
 | 
			
		||||
 | 
			
		||||
    # then b
 | 
			
		||||
    b = next(u for u in bd if u != d)
 | 
			
		||||
 | 
			
		||||
    # f is in the three 6-length digits, while c is only in 2
 | 
			
		||||
    f = next(u for u in cf if all(u in p for p in per_length[6]))
 | 
			
		||||
 | 
			
		||||
    # c is not f
 | 
			
		||||
    c = next(u for u in cf if u != f)
 | 
			
		||||
 | 
			
		||||
    # e is the last one
 | 
			
		||||
    e = next(u for u in "abcdefg" if u not in {a, b, c, d, f, g})
 | 
			
		||||
 | 
			
		||||
    mapping = dict(zip((a, b, c, d, e, f, g), "abcdefg"))
 | 
			
		||||
 | 
			
		||||
    value = 0
 | 
			
		||||
    for number in parts[1].strip().split():
 | 
			
		||||
        digit = "".join(sorted(mapping[c] for c in number))
 | 
			
		||||
        value = 10 * value + digits[digit]
 | 
			
		||||
 | 
			
		||||
    if VERBOSE:
 | 
			
		||||
        print(value)
 | 
			
		||||
 | 
			
		||||
    values.append(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
answer_2 = sum(values)
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										13
									
								
								2021/day9.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								2021/day9.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = ...
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
answer_2 = ...
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										7
									
								
								2022/day1.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								2022/day1.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
blocks = sys.stdin.read().split("\n\n")
 | 
			
		||||
values = sorted(sum(map(int, block.split())) for block in blocks)
 | 
			
		||||
 | 
			
		||||
print(f"answer 1 is {values[-1]}")
 | 
			
		||||
print(f"answer 2 is {sum(values[-3:])}")
 | 
			
		||||
							
								
								
									
										38
									
								
								2022/day10.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								2022/day10.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
cycle = 1
 | 
			
		||||
x = 1
 | 
			
		||||
 | 
			
		||||
values = {cycle: x}
 | 
			
		||||
 | 
			
		||||
for line in lines:
 | 
			
		||||
    cycle += 1
 | 
			
		||||
 | 
			
		||||
    if line == "noop":
 | 
			
		||||
        pass
 | 
			
		||||
    else:
 | 
			
		||||
        r = int(line.split()[1])
 | 
			
		||||
 | 
			
		||||
        values[cycle] = x
 | 
			
		||||
 | 
			
		||||
        cycle += 1
 | 
			
		||||
        x += r
 | 
			
		||||
 | 
			
		||||
    values[cycle] = x
 | 
			
		||||
 | 
			
		||||
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
for i in range(6):
 | 
			
		||||
    for j in range(40):
 | 
			
		||||
        v = values[1 + i * 40 + j]
 | 
			
		||||
 | 
			
		||||
        if j >= v - 1 and j <= v + 1:
 | 
			
		||||
            print("#", end="")
 | 
			
		||||
        else:
 | 
			
		||||
            print(".", end="")
 | 
			
		||||
 | 
			
		||||
    print()
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
import copy
 | 
			
		||||
import sys
 | 
			
		||||
from functools import reduce
 | 
			
		||||
from typing import Any, Callable, Final, Iterator, Mapping, Sequence
 | 
			
		||||
 | 
			
		||||
from ..base import BaseSolver
 | 
			
		||||
from typing import Callable, Final, Mapping, Sequence
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Monkey:
 | 
			
		||||
@@ -120,28 +119,24 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
 | 
			
		||||
    return sorted_levels[-2] * sorted_levels[-1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Solver(BaseSolver):
 | 
			
		||||
    def solve(self, input: str) -> Iterator[Any]:
 | 
			
		||||
        monkeys = [parse_monkey(block.splitlines()) for block in input.split("\n\n")]
 | 
			
		||||
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
 | 
			
		||||
 | 
			
		||||
        # case 1: we simply divide the worry by 3 after applying the monkey worry operation
 | 
			
		||||
        yield monkey_business(
 | 
			
		||||
            run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
 | 
			
		||||
        )
 | 
			
		||||
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
 | 
			
		||||
answer_1 = monkey_business(
 | 
			
		||||
    run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
        # case 2: to keep reasonable level values, we can use a modulo operation, we need to
 | 
			
		||||
        # use the product of all "divisible by" test so that the test remains valid
 | 
			
		||||
        #
 | 
			
		||||
        # (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
 | 
			
		||||
        #
 | 
			
		||||
        # (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
 | 
			
		||||
        # we use the product of all test value
 | 
			
		||||
        #
 | 
			
		||||
        total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
 | 
			
		||||
        yield monkey_business(
 | 
			
		||||
            run(
 | 
			
		||||
                copy.deepcopy(monkeys),
 | 
			
		||||
                10_000,
 | 
			
		||||
                me_worry_fn=lambda w: w % total_test_value,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
 | 
			
		||||
# use the product of all "divisible by" test so that the test remains valid
 | 
			
		||||
#
 | 
			
		||||
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
 | 
			
		||||
#
 | 
			
		||||
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
 | 
			
		||||
# we use the product of all test value
 | 
			
		||||
#
 | 
			
		||||
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
 | 
			
		||||
answer_2 = monkey_business(
 | 
			
		||||
    run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										160
									
								
								2022/day12.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								2022/day12.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
			
		||||
import heapq
 | 
			
		||||
import sys
 | 
			
		||||
from typing import Callable, Iterator, TypeVar
 | 
			
		||||
 | 
			
		||||
Node = TypeVar("Node")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dijkstra(
 | 
			
		||||
    start: Node,
 | 
			
		||||
    neighbors: Callable[[Node], Iterator[Node]],
 | 
			
		||||
    cost: Callable[[Node, Node], float],
 | 
			
		||||
) -> tuple[dict[Node, float], dict[Node, Node]]:
 | 
			
		||||
    """
 | 
			
		||||
    Compute shortest paths from one node to all reachable ones.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        start: Starting node.
 | 
			
		||||
        neighbors: Function returning the neighbors of a node.
 | 
			
		||||
        cost: Function to compute the cost of an edge.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        A tuple (lengths, parents) where lengths is a mapping from Node to distance
 | 
			
		||||
        (from the starting node) and parents a mapping from parents Node (in the
 | 
			
		||||
        shortest path). If keyset of lengths and parents is the same. If a Node is not
 | 
			
		||||
        in the mapping, it cannot be reached from the starting node.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    queue: list[tuple[float, Node]] = []
 | 
			
		||||
 | 
			
		||||
    visited: set[Node] = set()
 | 
			
		||||
    lengths: dict[Node, float] = {start: 0}
 | 
			
		||||
    parents: dict[Node, Node] = {}
 | 
			
		||||
 | 
			
		||||
    heapq.heappush(queue, (0, start))
 | 
			
		||||
 | 
			
		||||
    while queue:
 | 
			
		||||
        length, current = heapq.heappop(queue)
 | 
			
		||||
 | 
			
		||||
        if current in visited:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        visited.add(current)
 | 
			
		||||
 | 
			
		||||
        for neighbor in neighbors(current):
 | 
			
		||||
            if neighbor in visited:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            neighbor_cost = length + cost(current, neighbor)
 | 
			
		||||
 | 
			
		||||
            if neighbor_cost < lengths.get(neighbor, float("inf")):
 | 
			
		||||
                lengths[neighbor] = neighbor_cost
 | 
			
		||||
                parents[neighbor] = current
 | 
			
		||||
 | 
			
		||||
                heapq.heappush(queue, (neighbor_cost, neighbor))
 | 
			
		||||
 | 
			
		||||
    return lengths, parents
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_path(parents: dict[Node, Node], start: Node, end: Node) -> list[Node] | None:
 | 
			
		||||
    if end not in parents:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    path: list[Node] = [end]
 | 
			
		||||
 | 
			
		||||
    while path[-1] is not start:
 | 
			
		||||
        path.append(parents[path[-1]])
 | 
			
		||||
 | 
			
		||||
    return list(reversed(path))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_path(path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
 | 
			
		||||
    end = path[-1]
 | 
			
		||||
 | 
			
		||||
    graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
 | 
			
		||||
    graph[end[0]][end[1]] = "E"
 | 
			
		||||
 | 
			
		||||
    for i in range(0, len(path) - 1):
 | 
			
		||||
        cr, cc = path[i]
 | 
			
		||||
        nr, nc = path[i + 1]
 | 
			
		||||
 | 
			
		||||
        if cr == nr and nc == cc - 1:
 | 
			
		||||
            graph[cr][cc] = "<"
 | 
			
		||||
        elif cr == nr and nc == cc + 1:
 | 
			
		||||
            graph[cr][cc] = ">"
 | 
			
		||||
        elif cr == nr - 1 and nc == cc:
 | 
			
		||||
            graph[cr][cc] = "v"
 | 
			
		||||
        elif cr == nr + 1 and nc == cc:
 | 
			
		||||
            graph[cr][cc] = "^"
 | 
			
		||||
        else:
 | 
			
		||||
            assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
 | 
			
		||||
 | 
			
		||||
    print("\n".join("".join(row) for row in graph))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def neighbors(
 | 
			
		||||
    grid: list[list[int]], node: tuple[int, int], up: bool
 | 
			
		||||
) -> Iterator[tuple[int, int]]:
 | 
			
		||||
    n_rows = len(grid)
 | 
			
		||||
    n_cols = len(grid[0])
 | 
			
		||||
 | 
			
		||||
    c_row, c_col = node
 | 
			
		||||
    for n_row, n_col in (
 | 
			
		||||
        (c_row - 1, c_col),
 | 
			
		||||
        (c_row + 1, c_col),
 | 
			
		||||
        (c_row, c_col - 1),
 | 
			
		||||
        (c_row, c_col + 1),
 | 
			
		||||
    ):
 | 
			
		||||
        if not (n_row >= 0 and n_row < n_rows and n_col >= 0 and n_col < n_cols):
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        if up and grid[n_row][n_col] > grid[c_row][c_col] + 1:
 | 
			
		||||
            continue
 | 
			
		||||
        elif not up and grid[n_row][n_col] < grid[c_row][c_col] - 1:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        yield n_row, n_col
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# === main code ===
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
 | 
			
		||||
 | 
			
		||||
start: tuple[int, int]
 | 
			
		||||
end: tuple[int, int]
 | 
			
		||||
 | 
			
		||||
# for part 2
 | 
			
		||||
start_s: list[tuple[int, int]] = []
 | 
			
		||||
 | 
			
		||||
for i_row, row in enumerate(grid):
 | 
			
		||||
    for i_col, col in enumerate(row):
 | 
			
		||||
        if chr(col + ord("a")) == "S":
 | 
			
		||||
            start = (i_row, i_col)
 | 
			
		||||
            start_s.append(start)
 | 
			
		||||
        elif chr(col + ord("a")) == "E":
 | 
			
		||||
            end = (i_row, i_col)
 | 
			
		||||
        elif col == 0:
 | 
			
		||||
            start_s.append((i_row, i_col))
 | 
			
		||||
 | 
			
		||||
# fix values
 | 
			
		||||
grid[start[0]][start[1]] = 0
 | 
			
		||||
grid[end[0]][end[1]] = ord("z") - ord("a")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lengths_1, parents_1 = dijkstra(
 | 
			
		||||
    start=start, neighbors=lambda n: neighbors(grid, n, True), cost=lambda lhs, rhs: 1
 | 
			
		||||
)
 | 
			
		||||
path_1 = make_path(parents_1, start, end)
 | 
			
		||||
assert path_1 is not None
 | 
			
		||||
 | 
			
		||||
print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
 | 
			
		||||
 | 
			
		||||
print(f"answer 1 is {lengths_1[end] - 1}")
 | 
			
		||||
 | 
			
		||||
lengths_2, parents_2 = dijkstra(
 | 
			
		||||
    start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1
 | 
			
		||||
)
 | 
			
		||||
answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s)
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
import json
 | 
			
		||||
import sys
 | 
			
		||||
from functools import cmp_to_key
 | 
			
		||||
from typing import Any, Iterator, TypeAlias, cast
 | 
			
		||||
from typing import TypeAlias, cast
 | 
			
		||||
 | 
			
		||||
from ..base import BaseSolver
 | 
			
		||||
blocks = sys.stdin.read().strip().split("\n\n")
 | 
			
		||||
 | 
			
		||||
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
 | 
			
		||||
 | 
			
		||||
Packet: TypeAlias = list[int | list["Packet"]]
 | 
			
		||||
 | 
			
		||||
@@ -25,18 +28,14 @@ def compare(lhs: Packet, rhs: Packet) -> int:
 | 
			
		||||
    return len(rhs) - len(lhs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Solver(BaseSolver):
 | 
			
		||||
    def solve(self, input: str) -> Iterator[Any]:
 | 
			
		||||
        blocks = input.split("\n\n")
 | 
			
		||||
        pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
 | 
			
		||||
answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
 | 
			
		||||
print(f"answer_1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
        yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
 | 
			
		||||
dividers = [[[2]], [[6]]]
 | 
			
		||||
 | 
			
		||||
        dividers = [[[2]], [[6]]]
 | 
			
		||||
packets = [packet for packets in pairs for packet in packets]
 | 
			
		||||
packets.extend(dividers)
 | 
			
		||||
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
 | 
			
		||||
 | 
			
		||||
        packets = [packet for packets in pairs for packet in packets]
 | 
			
		||||
        packets.extend(dividers)
 | 
			
		||||
        packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
 | 
			
		||||
 | 
			
		||||
        d_index = [packets.index(d) + 1 for d in dividers]
 | 
			
		||||
        yield d_index[0] * d_index[1]
 | 
			
		||||
d_index = [packets.index(d) + 1 for d in dividers]
 | 
			
		||||
print(f"answer 2 is {d_index[0] * d_index[1]}")
 | 
			
		||||
							
								
								
									
										140
									
								
								2022/day14.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								2022/day14.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
import sys
 | 
			
		||||
from enum import Enum, auto
 | 
			
		||||
from typing import Callable, cast
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Cell(Enum):
 | 
			
		||||
    AIR = auto()
 | 
			
		||||
    ROCK = auto()
 | 
			
		||||
    SAND = auto()
 | 
			
		||||
 | 
			
		||||
    def __str__(self) -> str:
 | 
			
		||||
        return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_blocks(blocks: dict[tuple[int, int], Cell]):
 | 
			
		||||
    """
 | 
			
		||||
    Print the given set of blocks on a grid.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        blocks: Set of blocks to print.
 | 
			
		||||
    """
 | 
			
		||||
    x_min, y_min, x_max, y_max = (
 | 
			
		||||
        min(x for x, _ in blocks),
 | 
			
		||||
        0,
 | 
			
		||||
        max(x for x, _ in blocks),
 | 
			
		||||
        max(y for _, y in blocks),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for y in range(y_min, y_max + 1):
 | 
			
		||||
        print(
 | 
			
		||||
            "".join(str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1))
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def flow(
 | 
			
		||||
    blocks: dict[tuple[int, int], Cell],
 | 
			
		||||
    stop_fn: Callable[[int, int], bool],
 | 
			
		||||
    fill_fn: Callable[[int, int], Cell],
 | 
			
		||||
) -> dict[tuple[int, int], Cell]:
 | 
			
		||||
    """
 | 
			
		||||
    Flow sands onto the given set of blocks
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        blocks: Blocks containing ROCK position. Modified in-place.
 | 
			
		||||
        stop_fn: Function called with the last (assumed) position of a grain of
 | 
			
		||||
            sand BEFORE adding it to blocks. If the function returns True, the grain
 | 
			
		||||
            is added and a new one is flowed, otherwise, the whole procedure stops
 | 
			
		||||
            and the function returns (without adding the final grain).
 | 
			
		||||
        fill_fn: Function called when the target position of a grain (during the
 | 
			
		||||
            flowing process) is missing from blocks.
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        The input blocks.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    y_max = max(y for _, y in blocks)
 | 
			
		||||
 | 
			
		||||
    while True:
 | 
			
		||||
        x, y = 500, 0
 | 
			
		||||
 | 
			
		||||
        while y <= y_max:
 | 
			
		||||
            moved = False
 | 
			
		||||
            for cx, cy in ((x, y + 1), (x - 1, y + 1), (x + 1, y + 1)):
 | 
			
		||||
                if (cx, cy) not in blocks and fill_fn(cx, cy) == Cell.AIR:
 | 
			
		||||
                    x, y = cx, cy
 | 
			
		||||
                    moved = True
 | 
			
		||||
                elif blocks[cx, cy] == Cell.AIR:
 | 
			
		||||
                    x, y = cx, cy
 | 
			
		||||
                    moved = True
 | 
			
		||||
 | 
			
		||||
                if moved:
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            if not moved:
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        if stop_fn(x, y):
 | 
			
		||||
            break
 | 
			
		||||
 | 
			
		||||
        blocks[x, y] = Cell.SAND
 | 
			
		||||
 | 
			
		||||
    return blocks
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# === inputs ===
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
paths: list[list[tuple[int, int]]] = []
 | 
			
		||||
for line in lines:
 | 
			
		||||
    parts = line.split(" -> ")
 | 
			
		||||
    paths.append(
 | 
			
		||||
        [
 | 
			
		||||
            cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
 | 
			
		||||
            for part in parts
 | 
			
		||||
        ]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
blocks: dict[tuple[int, int], Cell] = {}
 | 
			
		||||
for path in paths:
 | 
			
		||||
    for start, end in zip(path[:-1], path[1:]):
 | 
			
		||||
        x_start = min(start[0], end[0])
 | 
			
		||||
        x_end = max(start[0], end[0]) + 1
 | 
			
		||||
        y_start = min(start[1], end[1])
 | 
			
		||||
        y_end = max(start[1], end[1]) + 1
 | 
			
		||||
 | 
			
		||||
        for x in range(x_start, x_end):
 | 
			
		||||
            for y in range(y_start, y_end):
 | 
			
		||||
                blocks[x, y] = Cell.ROCK
 | 
			
		||||
 | 
			
		||||
print_blocks(blocks)
 | 
			
		||||
print()
 | 
			
		||||
 | 
			
		||||
x_min, y_min, x_max, y_max = (
 | 
			
		||||
    min(x for x, _ in blocks),
 | 
			
		||||
    0,
 | 
			
		||||
    max(x for x, _ in blocks),
 | 
			
		||||
    max(y for _, y in blocks),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# === part 1 ===
 | 
			
		||||
 | 
			
		||||
blocks_1 = flow(
 | 
			
		||||
    blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
 | 
			
		||||
)
 | 
			
		||||
print_blocks(blocks_1)
 | 
			
		||||
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
 | 
			
		||||
print()
 | 
			
		||||
 | 
			
		||||
# === part 2 ===
 | 
			
		||||
 | 
			
		||||
blocks_2 = flow(
 | 
			
		||||
    blocks.copy(),
 | 
			
		||||
    stop_fn=lambda x, y: x == 500 and y == 0,
 | 
			
		||||
    fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
 | 
			
		||||
)
 | 
			
		||||
blocks_2[500, 0] = Cell.SAND
 | 
			
		||||
print_blocks(blocks_2)
 | 
			
		||||
print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")
 | 
			
		||||
							
								
								
									
										87
									
								
								2022/day15.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								2022/day15.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
import parse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def part1(sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int) -> int:
 | 
			
		||||
    no_beacons_row_l: list[np.ndarray] = []
 | 
			
		||||
 | 
			
		||||
    for (sx, sy), (bx, by) in sensor_to_beacon.items():
 | 
			
		||||
        d = abs(sx - bx) + abs(sy - by)  # closest
 | 
			
		||||
 | 
			
		||||
        no_beacons_row_l.append(sx - np.arange(0, d - abs(sy - row) + 1))
 | 
			
		||||
        no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1))
 | 
			
		||||
 | 
			
		||||
    beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row)
 | 
			
		||||
    no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference(beacons_at_row)
 | 
			
		||||
 | 
			
		||||
    return len(no_beacons_row)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def part2_intervals(
 | 
			
		||||
    sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
 | 
			
		||||
) -> tuple[int, int, int]:
 | 
			
		||||
    from tqdm import trange
 | 
			
		||||
 | 
			
		||||
    for y in trange(xy_max + 1):
 | 
			
		||||
        its: list[tuple[int, int]] = []
 | 
			
		||||
        for (sx, sy), (bx, by) in sensor_to_beacon.items():
 | 
			
		||||
            d = abs(sx - bx) + abs(sy - by)
 | 
			
		||||
            dx = d - abs(sy - y)
 | 
			
		||||
 | 
			
		||||
            if dx >= 0:
 | 
			
		||||
                its.append((max(0, sx - dx), min(sx + dx, xy_max)))
 | 
			
		||||
 | 
			
		||||
        its = sorted(its)
 | 
			
		||||
        _, e = its[0]
 | 
			
		||||
 | 
			
		||||
        for si, ei in its[1:]:
 | 
			
		||||
            if si > e + 1:
 | 
			
		||||
                return si - 1, y, 4_000_000 * (si - 1) + y
 | 
			
		||||
            if ei > e:
 | 
			
		||||
                e = ei
 | 
			
		||||
 | 
			
		||||
    return (0, 0, 0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def part2_cplex(
 | 
			
		||||
    sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
 | 
			
		||||
) -> tuple[int, int, int]:
 | 
			
		||||
    from docplex.mp.model import Model
 | 
			
		||||
 | 
			
		||||
    m = Model()
 | 
			
		||||
 | 
			
		||||
    x, y = m.continuous_var_list(2, ub=xy_max, name=["x", "y"])
 | 
			
		||||
 | 
			
		||||
    for (sx, sy), (bx, by) in sensor_to_beacon.items():
 | 
			
		||||
        d = abs(sx - bx) + abs(sy - by)
 | 
			
		||||
        m.add_constraint(m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}")
 | 
			
		||||
 | 
			
		||||
    m.set_objective("min", x + y)
 | 
			
		||||
 | 
			
		||||
    s = m.solve()
 | 
			
		||||
 | 
			
		||||
    vx = int(s.get_value(x))
 | 
			
		||||
    vy = int(s.get_value(y))
 | 
			
		||||
    return vx, vy, 4_000_000 * vx + vy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {}
 | 
			
		||||
 | 
			
		||||
for line in lines:
 | 
			
		||||
    r = parse.parse(
 | 
			
		||||
        "Sensor at x={sx}, y={sy}: closest beacon is at x={bx}, y={by}", line
 | 
			
		||||
    )
 | 
			
		||||
    sensor_to_beacon[int(r["sx"]), int(r["sy"])] = (int(r["bx"]), int(r["by"]))
 | 
			
		||||
 | 
			
		||||
xy_max = 4_000_000 if max(sensor_to_beacon) > (1_000, 0) else 20
 | 
			
		||||
row = 2_000_000 if max(sensor_to_beacon) > (1_000, 0) else 10
 | 
			
		||||
 | 
			
		||||
print(f"answer 1 is {part1(sensor_to_beacon, row)}")
 | 
			
		||||
 | 
			
		||||
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
 | 
			
		||||
x, y, a2 = part2_intervals(sensor_to_beacon, xy_max)
 | 
			
		||||
print(f"answer 2 is {a2} (x={x}, y={y})")
 | 
			
		||||
							
								
								
									
										158
									
								
								2022/day16.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								2022/day16.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
import heapq
 | 
			
		||||
import itertools
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from typing import FrozenSet, NamedTuple
 | 
			
		||||
 | 
			
		||||
from tqdm import tqdm
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Pipe(NamedTuple):
 | 
			
		||||
    name: str
 | 
			
		||||
    flow: int
 | 
			
		||||
    tunnels: list[str]
 | 
			
		||||
 | 
			
		||||
    def __lt__(self, other: object) -> bool:
 | 
			
		||||
        return isinstance(other, Pipe) and other.name < self.name
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other: object) -> bool:
 | 
			
		||||
        return isinstance(other, Pipe) and other.name == self.name
 | 
			
		||||
 | 
			
		||||
    def __hash__(self) -> int:
 | 
			
		||||
        return hash(self.name)
 | 
			
		||||
 | 
			
		||||
    def __str__(self) -> str:
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
    def __repr__(self) -> str:
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def breadth_first_search(pipes: dict[str, Pipe], pipe: Pipe) -> dict[Pipe, int]:
 | 
			
		||||
    """
 | 
			
		||||
    Runs a BFS from the given pipe and return the shortest distance (in term of hops)
 | 
			
		||||
    to all other pipes.
 | 
			
		||||
    """
 | 
			
		||||
    queue = [(0, pipe_1)]
 | 
			
		||||
    visited = set()
 | 
			
		||||
    distances: dict[Pipe, int] = {}
 | 
			
		||||
 | 
			
		||||
    while len(distances) < len(pipes):
 | 
			
		||||
        distance, current = heapq.heappop(queue)
 | 
			
		||||
 | 
			
		||||
        if current in visited:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        visited.add(current)
 | 
			
		||||
        distances[current] = distance
 | 
			
		||||
 | 
			
		||||
        for tunnel in current.tunnels:
 | 
			
		||||
            heapq.heappush(queue, (distance + 1, pipes[tunnel]))
 | 
			
		||||
 | 
			
		||||
    return distances
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def update_with_better(
 | 
			
		||||
    node_at_times: dict[FrozenSet[Pipe], int], flow: int, flowing: FrozenSet[Pipe]
 | 
			
		||||
) -> None:
 | 
			
		||||
    node_at_times[flowing] = max(node_at_times[flowing], flow)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def part_1(
 | 
			
		||||
    start_pipe: Pipe,
 | 
			
		||||
    max_time: int,
 | 
			
		||||
    distances: dict[tuple[Pipe, Pipe], int],
 | 
			
		||||
    relevant_pipes: FrozenSet[Pipe],
 | 
			
		||||
):
 | 
			
		||||
    node_at_times: dict[int, dict[Pipe, dict[FrozenSet[Pipe], int]]] = defaultdict(
 | 
			
		||||
        lambda: defaultdict(lambda: defaultdict(lambda: 0))
 | 
			
		||||
    )
 | 
			
		||||
    node_at_times[0] = {start_pipe: {frozenset(): 0}}
 | 
			
		||||
 | 
			
		||||
    for time in range(max_time):
 | 
			
		||||
        for c_pipe, nodes in node_at_times[time].items():
 | 
			
		||||
            for flowing, flow in nodes.items():
 | 
			
		||||
                for target in relevant_pipes:
 | 
			
		||||
                    distance = distances[c_pipe, target] + 1
 | 
			
		||||
                    if time + distance >= max_time or target in flowing:
 | 
			
		||||
                        continue
 | 
			
		||||
 | 
			
		||||
                    update_with_better(
 | 
			
		||||
                        node_at_times[time + distance][target],
 | 
			
		||||
                        flow + sum(pipe.flow for pipe in flowing) * distance,
 | 
			
		||||
                        flowing | {target},
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                update_with_better(
 | 
			
		||||
                    node_at_times[max_time][c_pipe],
 | 
			
		||||
                    flow + sum(pipe.flow for pipe in flowing) * (max_time - time),
 | 
			
		||||
                    flowing,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    return max(
 | 
			
		||||
        flow
 | 
			
		||||
        for nodes_of_pipe in node_at_times[max_time].values()
 | 
			
		||||
        for flow in nodes_of_pipe.values()
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def part_2(
 | 
			
		||||
    start_pipe: Pipe,
 | 
			
		||||
    max_time: int,
 | 
			
		||||
    distances: dict[tuple[Pipe, Pipe], int],
 | 
			
		||||
    relevant_pipes: FrozenSet[Pipe],
 | 
			
		||||
):
 | 
			
		||||
    def compute(pipes_for_me: FrozenSet[Pipe]) -> int:
 | 
			
		||||
        return part_1(start_pipe, max_time, distances, pipes_for_me) + part_1(
 | 
			
		||||
            start_pipe, max_time, distances, relevant_pipes - pipes_for_me
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    combs = [
 | 
			
		||||
        frozenset(relevant_pipes_1)
 | 
			
		||||
        for r in range(2, len(relevant_pipes) // 2 + 1)
 | 
			
		||||
        for relevant_pipes_1 in itertools.combinations(relevant_pipes, r)
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    return max(compute(comb) for comb in tqdm(combs))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# === MAIN ===
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pipes: dict[str, Pipe] = {}
 | 
			
		||||
for line in lines:
 | 
			
		||||
    r = re.match(
 | 
			
		||||
        R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
 | 
			
		||||
        line,
 | 
			
		||||
    )
 | 
			
		||||
    assert r
 | 
			
		||||
 | 
			
		||||
    g = r.groups()
 | 
			
		||||
 | 
			
		||||
    pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", "))
 | 
			
		||||
 | 
			
		||||
# compute distances from one valve to any other
 | 
			
		||||
distances: dict[tuple[Pipe, Pipe], int] = {}
 | 
			
		||||
for pipe_1 in pipes.values():
 | 
			
		||||
    distances.update(
 | 
			
		||||
        {
 | 
			
		||||
            (pipe_1, pipe_2): distance
 | 
			
		||||
            for pipe_2, distance in breadth_first_search(pipes, pipe_1).items()
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
# valves with flow
 | 
			
		||||
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 1651, 1653
 | 
			
		||||
print(part_1(pipes["AA"], 30, distances, relevant_pipes))
 | 
			
		||||
 | 
			
		||||
# 1707, 2223
 | 
			
		||||
print(part_2(pipes["AA"], 26, distances, relevant_pipes))
 | 
			
		||||
@@ -1,16 +1,23 @@
 | 
			
		||||
from typing import Any, Iterator, Sequence, TypeAlias, TypeVar
 | 
			
		||||
import sys
 | 
			
		||||
from typing import Sequence, TypeVar
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
from numpy.typing import NDArray
 | 
			
		||||
 | 
			
		||||
from ..base import BaseSolver
 | 
			
		||||
 | 
			
		||||
T = TypeVar("T")
 | 
			
		||||
 | 
			
		||||
Tower: TypeAlias = NDArray[np.bool]
 | 
			
		||||
 | 
			
		||||
def print_tower(tower: np.ndarray, out: str = "#"):
 | 
			
		||||
    print("-" * (tower.shape[1] + 2))
 | 
			
		||||
    non_empty = False
 | 
			
		||||
    for row in reversed(range(1, tower.shape[0])):
 | 
			
		||||
        if not non_empty and not tower[row, :].any():
 | 
			
		||||
            continue
 | 
			
		||||
        non_empty = True
 | 
			
		||||
        print("|" + "".join(out if c else "." for c in tower[row, :]) + "|")
 | 
			
		||||
    print("+" + "-" * tower.shape[1] + "+")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def tower_height(tower: Tower) -> int:
 | 
			
		||||
def tower_height(tower: np.ndarray) -> int:
 | 
			
		||||
    return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -38,8 +45,8 @@ def build_tower(
 | 
			
		||||
    n_rocks: int,
 | 
			
		||||
    jets: str,
 | 
			
		||||
    early_stop: bool = False,
 | 
			
		||||
    init: Tower = np.ones(WIDTH, dtype=bool),
 | 
			
		||||
) -> tuple[Tower, int, int, dict[int, int]]:
 | 
			
		||||
    init: np.ndarray = np.ones(WIDTH, dtype=bool),
 | 
			
		||||
) -> tuple[np.ndarray, int, int, dict[int, int]]:
 | 
			
		||||
    tower = EMPTY_BLOCKS.copy()
 | 
			
		||||
    tower[0, :] = init
 | 
			
		||||
 | 
			
		||||
@@ -88,24 +95,26 @@ def build_tower(
 | 
			
		||||
    return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Solver(BaseSolver):
 | 
			
		||||
    def solve(self, input: str) -> Iterator[Any]:
 | 
			
		||||
        tower, *_ = build_tower(2022, input)
 | 
			
		||||
        yield tower_height(tower)
 | 
			
		||||
line = sys.stdin.read().strip()
 | 
			
		||||
 | 
			
		||||
        TOTAL_ROCKS = 1_000_000_000_000
 | 
			
		||||
        _tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, input, True)
 | 
			
		||||
        assert prev_1 > 0
 | 
			
		||||
tower, *_ = build_tower(2022, line)
 | 
			
		||||
answer_1 = tower_height(tower)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
        # 2767 1513
 | 
			
		||||
        remaining_rocks = TOTAL_ROCKS - n_rocks_1
 | 
			
		||||
        n_repeat_rocks = n_rocks_1 - prev_1
 | 
			
		||||
        n_repeat_towers = remaining_rocks // n_repeat_rocks
 | 
			
		||||
TOTAL_ROCKS = 1_000_000_000_000
 | 
			
		||||
tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, line, True)
 | 
			
		||||
assert prev_1 > 0
 | 
			
		||||
 | 
			
		||||
        base_height = heights_1[prev_1]
 | 
			
		||||
        repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
 | 
			
		||||
        remaining_height = (
 | 
			
		||||
            heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
 | 
			
		||||
        )
 | 
			
		||||
# 2767 1513
 | 
			
		||||
remaining_rocks = TOTAL_ROCKS - n_rocks_1
 | 
			
		||||
n_repeat_rocks = n_rocks_1 - prev_1
 | 
			
		||||
n_repeat_towers = remaining_rocks // n_repeat_rocks
 | 
			
		||||
 | 
			
		||||
        yield base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
 | 
			
		||||
base_height = heights_1[prev_1]
 | 
			
		||||
repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
 | 
			
		||||
remaining_height = (
 | 
			
		||||
    heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
answer_2 = base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										51
									
								
								2022/day18.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								2022/day18.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import sys
 | 
			
		||||
from typing import FrozenSet
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
xyz = np.asarray(
 | 
			
		||||
    [
 | 
			
		||||
        tuple(int(x) for x in row.split(","))  # type: ignore
 | 
			
		||||
        for row in sys.stdin.read().splitlines()
 | 
			
		||||
    ]
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
xyz = xyz - xyz.min(axis=0) + 1
 | 
			
		||||
 | 
			
		||||
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
 | 
			
		||||
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
 | 
			
		||||
 | 
			
		||||
n_dims = len(cubes.shape)
 | 
			
		||||
 | 
			
		||||
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
 | 
			
		||||
 | 
			
		||||
answer_1 = sum(
 | 
			
		||||
    1 for x, y, z in xyz for dx, dy, dz in faces if not cubes[x + dx, y + dy, z + dz]
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
visited = np.zeros_like(cubes, dtype=bool)
 | 
			
		||||
queue = [(0, 0, 0)]
 | 
			
		||||
 | 
			
		||||
n_faces = 0
 | 
			
		||||
while queue:
 | 
			
		||||
    x, y, z = queue.pop(0)
 | 
			
		||||
 | 
			
		||||
    if visited[x, y, z]:
 | 
			
		||||
        continue
 | 
			
		||||
 | 
			
		||||
    visited[x, y, z] = True
 | 
			
		||||
 | 
			
		||||
    for dx, dy, dz in faces:
 | 
			
		||||
        nx, ny, nz = x + dx, y + dy, z + dz
 | 
			
		||||
        if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))):
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        if visited[nx, ny, nz]:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        if cubes[nx, ny, nz]:
 | 
			
		||||
            n_faces += 1
 | 
			
		||||
        else:
 | 
			
		||||
            queue.append((nx, ny, nz))
 | 
			
		||||
print(f"answer 2 is {n_faces}")
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
from typing import Any, Iterator, Literal
 | 
			
		||||
import sys
 | 
			
		||||
from typing import Literal
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
import parse  # pyright: ignore[reportMissingTypeStubs]
 | 
			
		||||
from numpy.typing import NDArray
 | 
			
		||||
 | 
			
		||||
from ..base import BaseSolver
 | 
			
		||||
import parse
 | 
			
		||||
from tqdm import tqdm
 | 
			
		||||
 | 
			
		||||
Reagent = Literal["ore", "clay", "obsidian", "geode"]
 | 
			
		||||
REAGENTS: tuple[Reagent, ...] = (
 | 
			
		||||
@@ -36,7 +35,7 @@ class State:
 | 
			
		||||
            self.robots = robots
 | 
			
		||||
            self.reagents = reagents
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other: object) -> bool:
 | 
			
		||||
    def __eq__(self, other) -> bool:
 | 
			
		||||
        return (
 | 
			
		||||
            isinstance(other, State)
 | 
			
		||||
            and self.robots == other.robots
 | 
			
		||||
@@ -63,6 +62,29 @@ def dominates(lhs: State, rhs: State):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
blueprints: list[dict[Reagent, IntOfReagent]] = []
 | 
			
		||||
for line in lines:
 | 
			
		||||
    r = parse.parse(
 | 
			
		||||
        "Blueprint {}: "
 | 
			
		||||
        "Each ore robot costs {:d} ore. "
 | 
			
		||||
        "Each clay robot costs {:d} ore. "
 | 
			
		||||
        "Each obsidian robot costs {:d} ore and {:d} clay. "
 | 
			
		||||
        "Each geode robot costs {:d} ore and {:d} obsidian.",
 | 
			
		||||
        line,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    blueprints.append(
 | 
			
		||||
        {
 | 
			
		||||
            "ore": {"ore": r[1]},
 | 
			
		||||
            "clay": {"ore": r[2]},
 | 
			
		||||
            "obsidian": {"ore": r[3], "clay": r[4]},
 | 
			
		||||
            "geode": {"ore": r[5], "obsidian": r[6]},
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
 | 
			
		||||
    # since we can only build one robot per time, we do not need more than X robots
 | 
			
		||||
    # of type K where X is the maximum number of K required among all robots, e.g.,
 | 
			
		||||
@@ -72,12 +94,11 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
 | 
			
		||||
        name: max(blueprint[r].get(name, 0) for r in REAGENTS) for name in REAGENTS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state_after_t: dict[int, set[State]] = {0: {State()}}
 | 
			
		||||
    state_after_t: dict[int, set[State]] = {0: [State()]}
 | 
			
		||||
 | 
			
		||||
    for t in range(1, max_time + 1):
 | 
			
		||||
        # list of new states at the end of step t that we are going to prune later
 | 
			
		||||
        states_for_t: set[State] = set()
 | 
			
		||||
        robots_that_can_be_built: list[Reagent]
 | 
			
		||||
 | 
			
		||||
        for state in state_after_t[t - 1]:
 | 
			
		||||
            robots_that_can_be_built = [
 | 
			
		||||
@@ -111,7 +132,7 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
 | 
			
		||||
            for robot in robots_that_can_be_built:
 | 
			
		||||
                robots = state.robots.copy()
 | 
			
		||||
                robots[robot] += 1
 | 
			
		||||
                reagents: IntOfReagent = {
 | 
			
		||||
                reagents = {
 | 
			
		||||
                    reagent: state.reagents[reagent]
 | 
			
		||||
                    + state.robots[reagent]
 | 
			
		||||
                    - blueprint[robot].get(reagent, 0)
 | 
			
		||||
@@ -130,7 +151,7 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
 | 
			
		||||
            ]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        to_keep: list[NDArray[np.integer[Any]]] = []
 | 
			
		||||
        to_keep = []
 | 
			
		||||
        while len(np_states) > 0:
 | 
			
		||||
            first_dom = (np_states[1:] >= np_states[0]).all(axis=1).any()
 | 
			
		||||
 | 
			
		||||
@@ -151,31 +172,11 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
 | 
			
		||||
    return max(state.reagents["geode"] for state in state_after_t[max_time])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Solver(BaseSolver):
 | 
			
		||||
    def solve(self, input: str) -> Iterator[Any]:
 | 
			
		||||
        blueprints: list[dict[Reagent, IntOfReagent]] = []
 | 
			
		||||
        for line in input.splitlines():
 | 
			
		||||
            r: list[int] = parse.parse(  # type: ignore
 | 
			
		||||
                "Blueprint {}: "
 | 
			
		||||
                "Each ore robot costs {:d} ore. "
 | 
			
		||||
                "Each clay robot costs {:d} ore. "
 | 
			
		||||
                "Each obsidian robot costs {:d} ore and {:d} clay. "
 | 
			
		||||
                "Each geode robot costs {:d} ore and {:d} obsidian.",
 | 
			
		||||
                line,
 | 
			
		||||
            )
 | 
			
		||||
answer_1 = sum(
 | 
			
		||||
    (i_blueprint + 1) * run(blueprint, 24)
 | 
			
		||||
    for i_blueprint, blueprint in enumerate(blueprints)
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
            blueprints.append(
 | 
			
		||||
                {
 | 
			
		||||
                    "ore": {"ore": r[1]},
 | 
			
		||||
                    "clay": {"ore": r[2]},
 | 
			
		||||
                    "obsidian": {"ore": r[3], "clay": r[4]},
 | 
			
		||||
                    "geode": {"ore": r[5], "obsidian": r[6]},
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        yield sum(
 | 
			
		||||
            (i_blueprint + 1) * run(blueprint, 24)
 | 
			
		||||
            for i_blueprint, blueprint in enumerate(blueprints)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        yield (run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32))
 | 
			
		||||
answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
@@ -1,6 +1,4 @@
 | 
			
		||||
from typing import Any, Iterator
 | 
			
		||||
 | 
			
		||||
from ..base import BaseSolver
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def score_1(ux: int, vx: int) -> int:
 | 
			
		||||
@@ -35,23 +33,21 @@ def score_2(ux: int, vx: int) -> int:
 | 
			
		||||
    return (ux + vx - 1) % 3 + 1 + vx * 3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Solver(BaseSolver):
 | 
			
		||||
    def solve(self, input: str) -> Iterator[Any]:
 | 
			
		||||
        lines = input.splitlines()
 | 
			
		||||
lines = sys.stdin.readlines()
 | 
			
		||||
 | 
			
		||||
        # the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
 | 
			
		||||
        # modulo-3 arithmetic
 | 
			
		||||
        #
 | 
			
		||||
        # in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
 | 
			
		||||
        # if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
 | 
			
		||||
        #
 | 
			
		||||
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
 | 
			
		||||
# modulo-3 arithmetic
 | 
			
		||||
#
 | 
			
		||||
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
 | 
			
		||||
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
        # we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
 | 
			
		||||
        # easier manipulation
 | 
			
		||||
        values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
 | 
			
		||||
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
 | 
			
		||||
# easier manipulation
 | 
			
		||||
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
 | 
			
		||||
 | 
			
		||||
        # part 1 - 13526
 | 
			
		||||
        yield sum(score_1(*v) for v in values)
 | 
			
		||||
# part 1 - 13526
 | 
			
		||||
print(f"answer 1 is {sum(score_1(*v) for v in values)}")
 | 
			
		||||
 | 
			
		||||
        # part 2 - 14204
 | 
			
		||||
        yield sum(score_2(*v) for v in values)
 | 
			
		||||
# part 2 - 14204
 | 
			
		||||
print(f"answer 2 is {sum(score_2(*v) for v in values)}")
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
from typing import Any, Iterator
 | 
			
		||||
 | 
			
		||||
from ..base import BaseSolver
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Number:
 | 
			
		||||
@@ -67,9 +65,10 @@ def decrypt(numbers: list[Number], key: int, rounds: int) -> int:
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Solver(BaseSolver):
 | 
			
		||||
    def solve(self, input: str) -> Iterator[Any]:
 | 
			
		||||
        numbers = [Number(int(x)) for x in input.splitlines()]
 | 
			
		||||
numbers = [Number(int(x)) for i, x in enumerate(sys.stdin.readlines())]
 | 
			
		||||
 | 
			
		||||
        yield decrypt(numbers, 1, 1)
 | 
			
		||||
        yield decrypt(numbers, 811589153, 10)
 | 
			
		||||
answer_1 = decrypt(numbers, 1, 1)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
answer_2 = decrypt(numbers, 811589153, 10)
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import operator
 | 
			
		||||
from typing import Any, Callable, Iterator
 | 
			
		||||
 | 
			
		||||
from ..base import BaseSolver
 | 
			
		||||
import sys
 | 
			
		||||
from typing import Callable
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
 | 
			
		||||
@@ -78,31 +77,31 @@ def invert(
 | 
			
		||||
    return monkeys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Solver(BaseSolver):
 | 
			
		||||
    def solve(self, input: str) -> Iterator[Any]:
 | 
			
		||||
        lines = [line.strip() for line in input.splitlines()]
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
        monkeys: dict[str, int | tuple[str, str, str]] = {}
 | 
			
		||||
monkeys: dict[str, int | tuple[str, str, str]] = {}
 | 
			
		||||
 | 
			
		||||
        op_monkeys: set[str] = set()
 | 
			
		||||
op_monkeys: set[str] = set()
 | 
			
		||||
 | 
			
		||||
        for line in lines:
 | 
			
		||||
            parts = line.split(":")
 | 
			
		||||
            name = parts[0].strip()
 | 
			
		||||
for line in lines:
 | 
			
		||||
    parts = line.split(":")
 | 
			
		||||
    name = parts[0].strip()
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                value = int(parts[1].strip())
 | 
			
		||||
                monkeys[name] = value
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                op1, ope, op2 = parts[1].strip().split()
 | 
			
		||||
                monkeys[name] = (op1, ope, op2)
 | 
			
		||||
    try:
 | 
			
		||||
        value = int(parts[1].strip())
 | 
			
		||||
        monkeys[name] = value
 | 
			
		||||
    except ValueError:
 | 
			
		||||
        op1, ope, op2 = parts[1].strip().split()
 | 
			
		||||
        monkeys[name] = (op1, ope, op2)
 | 
			
		||||
 | 
			
		||||
                op_monkeys.add(name)
 | 
			
		||||
        op_monkeys.add(name)
 | 
			
		||||
 | 
			
		||||
        yield compute(monkeys.copy(), "root")
 | 
			
		||||
 | 
			
		||||
        # assume the second operand of 'root' can be computed, and the first one depends on
 | 
			
		||||
        # humn, which is the case is my input and the test input
 | 
			
		||||
        assert isinstance(monkeys["root"], tuple)
 | 
			
		||||
        p1, _, p2 = monkeys["root"]  # type: ignore
 | 
			
		||||
        yield compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
 | 
			
		||||
answer_1 = compute(monkeys.copy(), "root")
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# assume the second operand of 'root' can be computed, and the first one depends on
 | 
			
		||||
# humn, which is the case is my input and the test input
 | 
			
		||||
p1, _, p2 = monkeys["root"]  # type: ignore
 | 
			
		||||
answer_2 = compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										223
									
								
								2022/day22.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								2022/day22.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,223 @@
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
from typing import Callable
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
 | 
			
		||||
VOID, EMPTY, WALL = 0, 1, 2
 | 
			
		||||
TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}
 | 
			
		||||
 | 
			
		||||
SCORES = {"E": 0, "S": 1, "W": 2, "N": 3}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
board_map_s, direction_s = sys.stdin.read().split("\n\n")
 | 
			
		||||
 | 
			
		||||
# board
 | 
			
		||||
board_lines = board_map_s.splitlines()
 | 
			
		||||
max_line = max(len(line) for line in board_lines)
 | 
			
		||||
board = np.array(
 | 
			
		||||
    [
 | 
			
		||||
        [TILE_FROM_CHAR[c] for c in row] + [VOID] * (max_line - len(row))
 | 
			
		||||
        for row in board_map_s.splitlines()
 | 
			
		||||
    ]
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
directions = [
 | 
			
		||||
    int(p1) if p2 else p1 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# find on each row and column the first and last non-void
 | 
			
		||||
row_first_non_void = np.argmax(board != VOID, axis=1)
 | 
			
		||||
row_last_non_void = board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1
 | 
			
		||||
col_first_non_void = np.argmax(board != VOID, axis=0)
 | 
			
		||||
col_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
faces = np.zeros_like(board)
 | 
			
		||||
size = np.gcd(board.shape[0], board.shape[1])
 | 
			
		||||
for row in range(0, board.shape[0], size):
 | 
			
		||||
    for col in range(row_first_non_void[row], row_last_non_void[row], size):
 | 
			
		||||
        faces[row : row + size, col : col + size] = faces.max() + 1
 | 
			
		||||
 | 
			
		||||
SIZE = np.gcd(*board.shape)
 | 
			
		||||
 | 
			
		||||
# TODO: deduce this from the actual cube...
 | 
			
		||||
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
 | 
			
		||||
 | 
			
		||||
if board.shape == (12, 16):  # example
 | 
			
		||||
    faces_wrap = {
 | 
			
		||||
        1: {
 | 
			
		||||
            "W": lambda y, x: (4, 4 + y, "S"),  # 3N
 | 
			
		||||
            "N": lambda y, x: (4, 11 - x, "S"),  # 2N
 | 
			
		||||
            "E": lambda y, x: (11 - y, 15, "W"),  # 6E
 | 
			
		||||
        },
 | 
			
		||||
        2: {
 | 
			
		||||
            "W": lambda y, x: (11, 19 - y, "N"),  # 6S
 | 
			
		||||
            "N": lambda y, x: (0, 11 - y, "S"),  # 1N
 | 
			
		||||
            "S": lambda y, x: (11, 11 - x, "N"),  # 5S
 | 
			
		||||
        },
 | 
			
		||||
        3: {
 | 
			
		||||
            "N": lambda y, x: (x - 4, 8, "E"),  # 1W
 | 
			
		||||
            "S": lambda y, x: (15 - x, 8, "E"),  # 5W
 | 
			
		||||
        },
 | 
			
		||||
        4: {"E": lambda y, x: (8, 19 - y, "S")},  # 6N
 | 
			
		||||
        5: {
 | 
			
		||||
            "W": lambda y, x: (7, 15 - y, "N"),  # 3S
 | 
			
		||||
            "S": lambda y, x: (7, 11 - x, "N"),  # 2S
 | 
			
		||||
        },
 | 
			
		||||
        6: {
 | 
			
		||||
            "N": lambda y, x: (19 - x, 11, "W"),  # 4E
 | 
			
		||||
            "E": lambda y, x: (11 - y, 11, "W"),  # 1E
 | 
			
		||||
            "S": lambda y, x: (19 - x, 0, "E"),  # 2W
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
else:
 | 
			
		||||
    faces_wrap = {
 | 
			
		||||
        1: {
 | 
			
		||||
            "W": lambda y, x: (3 * SIZE - y - 1, 0, "E"),  # 4W
 | 
			
		||||
            "N": lambda y, x: (2 * SIZE + x, 0, "E"),  # 6W
 | 
			
		||||
        },
 | 
			
		||||
        2: {
 | 
			
		||||
            "N": lambda y, x: (4 * SIZE - 1, x - 2 * SIZE, "N"),  # 6S
 | 
			
		||||
            "E": lambda y, x: (3 * SIZE - y - 1, 2 * SIZE - 1, "W"),  # 5E
 | 
			
		||||
            "S": lambda y, x: (x - SIZE, 2 * SIZE - 1, "W"),  # 3E
 | 
			
		||||
        },
 | 
			
		||||
        3: {
 | 
			
		||||
            "W": lambda y, x: (2 * SIZE, y - SIZE, "S"),  # 4N
 | 
			
		||||
            "E": lambda y, x: (SIZE - 1, SIZE + y, "N"),  # 2S
 | 
			
		||||
        },
 | 
			
		||||
        4: {
 | 
			
		||||
            "W": lambda y, x: (3 * SIZE - y - 1, SIZE, "E"),  # 1W
 | 
			
		||||
            "N": lambda y, x: (SIZE + x, SIZE, "E"),  # 3W
 | 
			
		||||
        },
 | 
			
		||||
        5: {
 | 
			
		||||
            "E": lambda y, x: (3 * SIZE - y - 1, 3 * SIZE - 1, "W"),  # 2E
 | 
			
		||||
            "S": lambda y, x: (2 * SIZE + x, SIZE - 1, "W"),  # 6E
 | 
			
		||||
        },
 | 
			
		||||
        6: {
 | 
			
		||||
            "W": lambda y, x: (0, y - 2 * SIZE, "S"),  # 1N
 | 
			
		||||
            "E": lambda y, x: (3 * SIZE - 1, y - 2 * SIZE, "N"),  # 5S
 | 
			
		||||
            "S": lambda y, x: (0, x + 2 * SIZE, "S"),  # 2N
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
 | 
			
		||||
    if r0 == "E":
 | 
			
		||||
        return y0, row_first_non_void[y0], r0
 | 
			
		||||
    elif r0 == "S":
 | 
			
		||||
        return col_first_non_void[x0], x0, r0
 | 
			
		||||
    elif r0 == "W":
 | 
			
		||||
        return y0, row_last_non_void[y0], r0
 | 
			
		||||
    elif r0 == "N":
 | 
			
		||||
        return col_last_non_void[x0], x0, r0
 | 
			
		||||
 | 
			
		||||
    assert False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
 | 
			
		||||
    cube = faces[y0, x0]
 | 
			
		||||
    assert r0 in faces_wrap[cube]
 | 
			
		||||
    return faces_wrap[cube][r0](y0, x0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int, str]:
 | 
			
		||||
    y0 = 0
 | 
			
		||||
    x0 = np.where(board[0] == EMPTY)[0][0]
 | 
			
		||||
    r0 = "E"
 | 
			
		||||
 | 
			
		||||
    for direction in directions:
 | 
			
		||||
        if isinstance(direction, int):
 | 
			
		||||
            while direction > 0:
 | 
			
		||||
                if r0 == "E":
 | 
			
		||||
                    xi = np.where(board[y0, x0 + 1 : x0 + direction + 1] == WALL)[0]
 | 
			
		||||
                    if len(xi):
 | 
			
		||||
                        x0 = x0 + xi[0]
 | 
			
		||||
                        direction = 0
 | 
			
		||||
                    elif (
 | 
			
		||||
                        x0 + direction < board.shape[1]
 | 
			
		||||
                        and board[y0, x0 + direction] == EMPTY
 | 
			
		||||
                    ):
 | 
			
		||||
                        x0 = x0 + direction
 | 
			
		||||
                        direction = 0
 | 
			
		||||
                    else:
 | 
			
		||||
                        y0_t, x0_t, r0_t = wrap(y0, x0, r0)
 | 
			
		||||
                        if board[y0_t, x0_t] == WALL:
 | 
			
		||||
                            x0 = row_last_non_void[y0]
 | 
			
		||||
                            direction = 0
 | 
			
		||||
                        else:
 | 
			
		||||
                            direction = direction - (row_last_non_void[y0] - x0) - 1
 | 
			
		||||
                            y0, x0, r0 = y0_t, x0_t, r0_t
 | 
			
		||||
                elif r0 == "S":
 | 
			
		||||
                    yi = np.where(board[y0 + 1 : y0 + direction + 1, x0] == WALL)[0]
 | 
			
		||||
                    if len(yi):
 | 
			
		||||
                        y0 = y0 + yi[0]
 | 
			
		||||
                        direction = 0
 | 
			
		||||
                    elif (
 | 
			
		||||
                        y0 + direction < board.shape[0]
 | 
			
		||||
                        and board[y0 + direction, x0] == EMPTY
 | 
			
		||||
                    ):
 | 
			
		||||
                        y0 = y0 + direction
 | 
			
		||||
                        direction = 0
 | 
			
		||||
                    else:
 | 
			
		||||
                        y0_t, x0_t, r0_t = wrap(y0, x0, r0)
 | 
			
		||||
                        if board[y0_t, x0_t] == WALL:
 | 
			
		||||
                            y0 = col_last_non_void[x0]
 | 
			
		||||
                            direction = 0
 | 
			
		||||
                        else:
 | 
			
		||||
                            direction = direction - (col_last_non_void[x0] - y0) - 1
 | 
			
		||||
                            y0, x0, r0 = y0_t, x0_t, r0_t
 | 
			
		||||
                elif r0 == "W":
 | 
			
		||||
                    left = max(x0 - direction - 1, 0)
 | 
			
		||||
                    xi = np.where(board[y0, left:x0] == WALL)[0]
 | 
			
		||||
                    if len(xi):
 | 
			
		||||
                        x0 = left + xi[-1] + 1
 | 
			
		||||
                        direction = 0
 | 
			
		||||
                    elif x0 - direction >= 0 and board[y0, x0 - direction] == EMPTY:
 | 
			
		||||
                        x0 = x0 - direction
 | 
			
		||||
                        direction = 0
 | 
			
		||||
                    else:
 | 
			
		||||
                        y0_t, x0_t, r0_t = wrap(y0, x0, r0)
 | 
			
		||||
                        if board[y0_t, x0_t] == WALL:
 | 
			
		||||
                            x0 = row_first_non_void[y0]
 | 
			
		||||
                            direction = 0
 | 
			
		||||
                        else:
 | 
			
		||||
                            direction = direction - (x0 - row_first_non_void[y0]) - 1
 | 
			
		||||
                            y0, x0, r0 = y0_t, x0_t, r0_t
 | 
			
		||||
                elif r0 == "N":
 | 
			
		||||
                    top = max(y0 - direction - 1, 0)
 | 
			
		||||
                    yi = np.where(board[top:y0, x0] == WALL)[0]
 | 
			
		||||
                    if len(yi):
 | 
			
		||||
                        y0 = top + yi[-1] + 1
 | 
			
		||||
                        direction = 0
 | 
			
		||||
                    elif y0 - direction >= 0 and board[y0 - direction, x0] == EMPTY:
 | 
			
		||||
                        y0 = y0 - direction
 | 
			
		||||
                        direction = 0
 | 
			
		||||
                    else:
 | 
			
		||||
                        y0_t, x0_t, r0_t = wrap(y0, x0, r0)
 | 
			
		||||
                        if board[y0_t, x0_t] == WALL:
 | 
			
		||||
                            y0 = col_first_non_void[x0]
 | 
			
		||||
                            direction = 0
 | 
			
		||||
                        else:
 | 
			
		||||
                            direction = direction - (y0 - col_first_non_void[x0]) - 1
 | 
			
		||||
                            y0, x0, r0 = y0_t, x0_t, r0_t
 | 
			
		||||
        else:
 | 
			
		||||
            r0 = {
 | 
			
		||||
                "E": {"L": "N", "R": "S"},
 | 
			
		||||
                "N": {"L": "W", "R": "E"},
 | 
			
		||||
                "W": {"L": "S", "R": "N"},
 | 
			
		||||
                "S": {"L": "E", "R": "W"},
 | 
			
		||||
            }[r0][direction]
 | 
			
		||||
 | 
			
		||||
    return y0, x0, r0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
y1, x1, r1 = run(wrap_part_1)
 | 
			
		||||
answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
y2, x2, r2 = run(wrap_part_2)
 | 
			
		||||
answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
import itertools
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from typing import Any, Iterator
 | 
			
		||||
 | 
			
		||||
from ..base import BaseSolver
 | 
			
		||||
 | 
			
		||||
Directions = list[
 | 
			
		||||
    tuple[
 | 
			
		||||
@@ -20,10 +18,22 @@ DIRECTIONS: Directions = [
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]:
 | 
			
		||||
    ys, xs = {y for y, _x in positions}, {x for _y, x in positions}
 | 
			
		||||
    ys, xs = {y for y, x in positions}, {x for y, x in positions}
 | 
			
		||||
    return min(ys), min(xs), max(ys), max(xs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_positions(positions: set[tuple[int, int]]):
 | 
			
		||||
    min_y, min_x, max_y, max_x = min_max_yx(positions)
 | 
			
		||||
    print(
 | 
			
		||||
        "\n".join(
 | 
			
		||||
            "".join(
 | 
			
		||||
                "#" if (y, x) in positions else "." for x in range(min_x - 1, max_x + 2)
 | 
			
		||||
            )
 | 
			
		||||
            for y in range(min_y - 1, max_y + 2)
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def round(
 | 
			
		||||
    positions: set[tuple[int, int]],
 | 
			
		||||
    directions: Directions,
 | 
			
		||||
@@ -59,38 +69,35 @@ def round(
 | 
			
		||||
    directions.append(directions.pop(0))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Solver(BaseSolver):
 | 
			
		||||
    def solve(self, input: str) -> Iterator[Any]:
 | 
			
		||||
        POSITIONS = {
 | 
			
		||||
            (i, j)
 | 
			
		||||
            for i, row in enumerate(input.splitlines())
 | 
			
		||||
            for j, col in enumerate(row)
 | 
			
		||||
            if col == "#"
 | 
			
		||||
        }
 | 
			
		||||
POSITIONS = {
 | 
			
		||||
    (i, j)
 | 
			
		||||
    for i, row in enumerate(sys.stdin.read().splitlines())
 | 
			
		||||
    for j, col in enumerate(row)
 | 
			
		||||
    if col == "#"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        # === part 1 ===
 | 
			
		||||
# === part 1 ===
 | 
			
		||||
 | 
			
		||||
        p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
 | 
			
		||||
        for _ in range(10):
 | 
			
		||||
            round(p1, d1)
 | 
			
		||||
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
 | 
			
		||||
for r in range(10):
 | 
			
		||||
    round(p1, d1)
 | 
			
		||||
 | 
			
		||||
        min_y, min_x, max_y, max_x = min_max_yx(p1)
 | 
			
		||||
        yield sum(
 | 
			
		||||
            (y, x) not in p1
 | 
			
		||||
            for y in range(min_y, max_y + 1)
 | 
			
		||||
            for x in range(min_x, max_x + 1)
 | 
			
		||||
        )
 | 
			
		||||
min_y, min_x, max_y, max_x = min_max_yx(p1)
 | 
			
		||||
answer_1 = sum(
 | 
			
		||||
    (y, x) not in p1 for y in range(min_y, max_y + 1) for x in range(min_x, max_x + 1)
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
        # === part 2 ===
 | 
			
		||||
# === part 2 ===
 | 
			
		||||
 | 
			
		||||
        p2, d2 = POSITIONS.copy(), DIRECTIONS.copy()
 | 
			
		||||
        answer_2 = 0
 | 
			
		||||
        while True:
 | 
			
		||||
            answer_2 += 1
 | 
			
		||||
            backup = p2.copy()
 | 
			
		||||
            round(p2, d2)
 | 
			
		||||
p2, d2 = POSITIONS.copy(), DIRECTIONS.copy()
 | 
			
		||||
answer_2 = 0
 | 
			
		||||
while True:
 | 
			
		||||
    answer_2 += 1
 | 
			
		||||
    backup = p2.copy()
 | 
			
		||||
    round(p2, d2)
 | 
			
		||||
 | 
			
		||||
            if backup == p2:
 | 
			
		||||
                break
 | 
			
		||||
    if backup == p2:
 | 
			
		||||
        break
 | 
			
		||||
 | 
			
		||||
        yield answer_2
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										98
									
								
								2022/day24.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								2022/day24.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
import heapq
 | 
			
		||||
import math
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
winds = {
 | 
			
		||||
    (i - 1, j - 1, lines[i][j])
 | 
			
		||||
    for i in range(1, len(lines) - 1)
 | 
			
		||||
    for j in range(1, len(lines[i]) - 1)
 | 
			
		||||
    if lines[i][j] != "."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
 | 
			
		||||
CYCLE = math.lcm(n_rows, n_cols)
 | 
			
		||||
 | 
			
		||||
east_winds = [{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)]
 | 
			
		||||
west_winds = [{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)]
 | 
			
		||||
north_winds = [
 | 
			
		||||
    {i for i in range(n_rows) if (i, j, "^") in winds} for j in range(n_cols)
 | 
			
		||||
]
 | 
			
		||||
south_winds = [
 | 
			
		||||
    {i for i in range(n_rows) if (i, j, "v") in winds} for j in range(n_cols)
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
 | 
			
		||||
    def heuristic(y: int, x: int) -> int:
 | 
			
		||||
        return abs(end[0] - y) + abs(end[1] - x)
 | 
			
		||||
 | 
			
		||||
    # (distance + heuristic, distance, (start_pos, cycle))
 | 
			
		||||
    queue = [(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))]
 | 
			
		||||
    visited: set[tuple[tuple[int, int], int]] = set()
 | 
			
		||||
    distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {})
 | 
			
		||||
 | 
			
		||||
    while queue:
 | 
			
		||||
        _, distance, ((y, x), cycle) = heapq.heappop(queue)
 | 
			
		||||
 | 
			
		||||
        if ((y, x), cycle) in visited:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        distances[y, x][cycle] = distance
 | 
			
		||||
 | 
			
		||||
        visited.add(((y, x), cycle))
 | 
			
		||||
 | 
			
		||||
        if (y, x) == (end[0], end[1]):
 | 
			
		||||
            break
 | 
			
		||||
 | 
			
		||||
        for dy, dx in (0, 0), (-1, 0), (1, 0), (0, -1), (0, 1):
 | 
			
		||||
            ty = y + dy
 | 
			
		||||
            tx = x + dx
 | 
			
		||||
 | 
			
		||||
            n_cycle = (cycle + 1) % CYCLE
 | 
			
		||||
 | 
			
		||||
            if (ty, tx) == end:
 | 
			
		||||
                heapq.heappush(queue, (distance + 1, distance + 1, ((ty, tx), n_cycle)))
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            if ((ty, tx), n_cycle) in visited:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if (ty, tx) != start and (ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if (ty, tx) != start:
 | 
			
		||||
                if (ty - n_cycle) % n_rows in south_winds[tx]:
 | 
			
		||||
                    continue
 | 
			
		||||
                if (ty + n_cycle) % n_rows in north_winds[tx]:
 | 
			
		||||
                    continue
 | 
			
		||||
                if (tx + n_cycle) % n_cols in west_winds[ty]:
 | 
			
		||||
                    continue
 | 
			
		||||
                if (tx - n_cycle) % n_cols in east_winds[ty]:
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
            heapq.heappush(
 | 
			
		||||
                queue,
 | 
			
		||||
                ((heuristic(ty, tx) + distance + 1, distance + 1, ((ty, tx), n_cycle))),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    return distances, next(iter(distances[end].values()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
start = (
 | 
			
		||||
    -1,
 | 
			
		||||
    next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
 | 
			
		||||
)
 | 
			
		||||
end = (
 | 
			
		||||
    n_rows,
 | 
			
		||||
    next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
distances_1, forward_1 = run(start, 0, end)
 | 
			
		||||
print(f"answer 1 is {forward_1}")
 | 
			
		||||
 | 
			
		||||
distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start)
 | 
			
		||||
distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
 | 
			
		||||
print(f"answer 2 is {forward_1 + return_1 + forward_2}")
 | 
			
		||||
							
								
								
									
										27
									
								
								2022/day25.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								2022/day25.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def snafu2number(number: str) -> int:
 | 
			
		||||
    value = 0
 | 
			
		||||
    for c in number:
 | 
			
		||||
        value *= 5
 | 
			
		||||
        value += coeffs[c]
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def number2snafu(number: int) -> str:
 | 
			
		||||
    values = ["0", "1", "2", "=", "-"]
 | 
			
		||||
    res = ""
 | 
			
		||||
    while number > 0:
 | 
			
		||||
        mod = number % 5
 | 
			
		||||
        res = res + values[mod]
 | 
			
		||||
        number = number // 5 + int(mod >= 3)
 | 
			
		||||
    return "".join(reversed(res))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
answer_1 = number2snafu(sum(map(snafu2number, lines)))
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
							
								
								
									
										23
									
								
								2022/day3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								2022/day3.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import string
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
lines = [line.strip() for line in sys.stdin.readlines()]
 | 
			
		||||
 | 
			
		||||
# extract content of each part
 | 
			
		||||
parts = [(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines]
 | 
			
		||||
 | 
			
		||||
# priorities
 | 
			
		||||
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
 | 
			
		||||
print(f"answer 1 is {part1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
n_per_group = 3
 | 
			
		||||
part2 = sum(
 | 
			
		||||
    priorities[c]
 | 
			
		||||
    for i in range(0, len(lines), n_per_group)
 | 
			
		||||
    for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group])
 | 
			
		||||
)
 | 
			
		||||
print(f"answer 2 is {part2}")
 | 
			
		||||
							
								
								
									
										17
									
								
								2022/day4.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								2022/day4.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
lines = [line.strip() for line in sys.stdin.readlines()]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_range(value: str) -> set[int]:
 | 
			
		||||
    parts = value.split("-")
 | 
			
		||||
    return set(range(int(parts[0]), int(parts[1]) + 1))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sections = [tuple(make_range(part) for part in line.split(",")) for line in lines]
 | 
			
		||||
 | 
			
		||||
answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections)
 | 
			
		||||
print(f"answer 1 is {answer_2}")
 | 
			
		||||
							
								
								
									
										41
									
								
								2022/day5.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								2022/day5.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import copy
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
blocks_s, moves_s = (part.splitlines() for part in sys.stdin.read().split("\n\n"))
 | 
			
		||||
 | 
			
		||||
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
 | 
			
		||||
 | 
			
		||||
# this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the
 | 
			
		||||
# form of '[X] ' (including the trailing space)
 | 
			
		||||
#
 | 
			
		||||
for block in blocks_s[-2::-1]:
 | 
			
		||||
    for stack, index in zip(blocks, range(0, len(block), 4)):
 | 
			
		||||
        crate = block[index + 1 : index + 2].strip()
 | 
			
		||||
 | 
			
		||||
        if crate:
 | 
			
		||||
            blocks[stack].append(crate)
 | 
			
		||||
 | 
			
		||||
# part 1 - deep copy for part 2
 | 
			
		||||
blocks_1 = copy.deepcopy(blocks)
 | 
			
		||||
 | 
			
		||||
for move in moves_s:
 | 
			
		||||
    _, count_s, _, from_, _, to_ = move.strip().split()
 | 
			
		||||
 | 
			
		||||
    for _i in range(int(count_s)):
 | 
			
		||||
        blocks_1[to_].append(blocks_1[from_].pop())
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
blocks_2 = copy.deepcopy(blocks)
 | 
			
		||||
 | 
			
		||||
for move in moves_s:
 | 
			
		||||
    _, count_s, _, from_, _, to_ = move.strip().split()
 | 
			
		||||
    count = int(count_s)
 | 
			
		||||
 | 
			
		||||
    blocks_2[to_].extend(blocks_2[from_][-count:])
 | 
			
		||||
    del blocks_2[from_][-count:]
 | 
			
		||||
 | 
			
		||||
answer_1 = "".join(s[-1] for s in blocks_1.values())
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
answer_2 = "".join(s[-1] for s in blocks_2.values())
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
							
								
								
									
										15
									
								
								2022/day6.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								2022/day6.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def index_of_first_n_differents(data: str, n: int) -> int:
 | 
			
		||||
    for i in range(len(data)):
 | 
			
		||||
        if len(set(data[i : i + n])) == n:
 | 
			
		||||
            return i + n
 | 
			
		||||
    return -1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
data = sys.stdin.read().strip()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
print(f"answer 1 is {index_of_first_n_differents(data, 4)}")
 | 
			
		||||
print(f"answer 2 is {index_of_first_n_differents(data, 14)}")
 | 
			
		||||
							
								
								
									
										80
									
								
								2022/day7.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								2022/day7.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
import sys
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
lines = sys.stdin.read().splitlines()
 | 
			
		||||
 | 
			
		||||
# we are going to use Path to create path and go up/down in the file tree since it
 | 
			
		||||
# implements everything we need
 | 
			
		||||
#
 | 
			
		||||
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
 | 
			
		||||
# on Windows but that is not an issue since only the sizes matter
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# mapping from path to list of files or directories
 | 
			
		||||
trees: dict[Path, list[Path]] = {}
 | 
			
		||||
 | 
			
		||||
# mapping from paths to either size (for file) or -1 for directory
 | 
			
		||||
sizes: dict[Path, int] = {}
 | 
			
		||||
 | 
			
		||||
# first line must be a cd otherwise we have no idea where we are
 | 
			
		||||
assert lines[0].startswith("$ cd")
 | 
			
		||||
base_path = Path(lines[0].strip("$").split()[1]).resolve()
 | 
			
		||||
cur_path = base_path
 | 
			
		||||
 | 
			
		||||
trees[cur_path] = []
 | 
			
		||||
sizes[cur_path] = -1
 | 
			
		||||
 | 
			
		||||
for line in lines[1:]:
 | 
			
		||||
    # command
 | 
			
		||||
    if line.startswith("$"):
 | 
			
		||||
        parts = line.strip("$").strip().split()
 | 
			
		||||
        command = parts[0]
 | 
			
		||||
 | 
			
		||||
        if command == "cd":
 | 
			
		||||
            cur_path = cur_path.joinpath(parts[1]).resolve()
 | 
			
		||||
 | 
			
		||||
            # just initialize the lis of files if not already done
 | 
			
		||||
            if cur_path not in trees:
 | 
			
		||||
                trees[cur_path] = []
 | 
			
		||||
        else:
 | 
			
		||||
            # nothing to do here
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    # fill the current path
 | 
			
		||||
    else:
 | 
			
		||||
        parts = line.split()
 | 
			
		||||
        name: str = parts[1]
 | 
			
		||||
        if line.startswith("dir"):
 | 
			
		||||
            size = -1
 | 
			
		||||
        else:
 | 
			
		||||
            size = int(parts[0])
 | 
			
		||||
 | 
			
		||||
        path = cur_path.joinpath(name)
 | 
			
		||||
        trees[cur_path].append(path)
 | 
			
		||||
        sizes[path] = size
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compute_size(path: Path) -> int:
 | 
			
		||||
    size = sizes[path]
 | 
			
		||||
 | 
			
		||||
    if size >= 0:
 | 
			
		||||
        return size
 | 
			
		||||
 | 
			
		||||
    return sum(compute_size(sub) for sub in trees[path])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
acc_sizes = {path: compute_size(path) for path in trees}
 | 
			
		||||
 | 
			
		||||
# part 1
 | 
			
		||||
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
 | 
			
		||||
print(f"answer 1 is {answer_1}")
 | 
			
		||||
 | 
			
		||||
# part 2
 | 
			
		||||
total_space = 70_000_000
 | 
			
		||||
update_space = 30_000_000
 | 
			
		||||
free_space = total_space - acc_sizes[base_path]
 | 
			
		||||
 | 
			
		||||
to_free_space = update_space - free_space
 | 
			
		||||
 | 
			
		||||
answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space)
 | 
			
		||||
print(f"answer 2 is {answer_2}")
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user