Compare commits

..

3 Commits

Author SHA1 Message Date
15b4f24087 Add 2023 day 24 2023-12-24 10:10:09 +01:00
ee031bdf67 2023 day 22: Slower code 2023-12-23 20:35:28 +01:00
5c55af6ae0 Factorise some code into snippets.py 2023-12-23 18:59:54 +01:00
9 changed files with 214 additions and 227 deletions

View File

@ -1,136 +1,20 @@
#!/usr/bin/python3 #!/usr/bin/python3
""" """
Jour 17 du défi Advent Of Code pour l'année 2022 Jour 17 du défi Advent Of Code pour l'année 2022
<!-- NE FONCTIONNE PAS --!>
""" """
import os
PRINT = True
ROCKS = [
[
["@", "@", "@", "@"]
],
[
[".", "@", "."],
["@", "@", "@"],
[".", "@", "."]
],
[
[".", ".", "@"],
[".", ".", "@"],
["@", "@", "@"]
],
[
["@"],
["@"],
["@"],
["@"]
],
[
["@", "@"],
["@", "@"],
]
]
def read_sample(): def read_sample():
"""récupère les entrées depuis le fichier texte correspondant""" """récupère les entrées depuis le fichier texte correspondant"""
with open('inputs/day17.txt', 'r') as f: filename = os.path.join(os.path.dirname(__file__ ), "inputs", "day17.txt")
return f.read() with open(filename, 'r') as f:
sample = f.read().split('\n')
sample = [ i for i in sample if i != '' ]
def print_puzzle(heights, rock, left_down_corner): return sample
if not PRINT:
return
for i in range(len(rock)):
print("|"+"."*left_down_corner[1]+"".join(rock[i])+"."*(7-len(rock[i])-left_down_corner[1])+"|")
for i in range(left_down_corner[0]-max(heights)):
print("|"+"."*7+"|")
for i in range(max(heights)):
print("|", end="")
for j in range(len(heights)):
if heights[j] >= (max(heights)-i):
print("#", end="")
else:
print(".", end="")
print("|")
print("+"+"-"*7+"+")
print()
def gas_jet(direction, pos, rock, largeur):
if direction == '>':
if pos[1]+max([len(j) for j in rock]) < largeur: #Move
pos = (pos[0], pos[1]+1)
print("Move right:")
else:
print("Failed to move right:")
else:
if pos[1] > 0: #Move
pos = (pos[0], pos[1]-1)
print("Move left:")
else:
print("Failed to move left:")
return pos
def part1(sample): def part1(sample):
"""Partie 1 du défi""" """Partie 1 du défi"""
largeur = 7 return NotImplementedError
heights = [0 for i in range(largeur)]
stream_pos = 0
for i in range(2022):
print(f"rock {i}")
rock = ROCKS[(i)%len(ROCKS)]
left_down_corner = (max(heights)+3, 2)
falling = True
while falling:
left_down_corner = gas_jet(sample[stream_pos%len(sample)], left_down_corner, rock, largeur)
stream_pos += 1
print_puzzle(heights, rock, left_down_corner)
print("falls 1 unit:")
left_down_corner = (left_down_corner[0]-1, left_down_corner[1])
print_puzzle(heights, rock, left_down_corner)
# Check si en bas
if left_down_corner[0] <= 0:
falling = False
elif left_down_corner[0] <= max(heights)+1:
print("Potentially blocked")
for j in range(len(rock[0])):
print(f"trying on rock[*][{j}]")
if (len(rock)-1) - max([k for k in range(len(rock)) if rock[k][j] != '.']) + left_down_corner[0] == heights[j+left_down_corner[1]]:
falling = False
print("blocked")
break
if falling:
print_puzzle(heights, rock, left_down_corner)
left_down_corner = gas_jet(sample[stream_pos%len(sample)], left_down_corner, rock, largeur)
stream_pos += 1
print_puzzle(heights, rock, left_down_corner)
for j in range(len(rock[0])):
(len(rock)-1) - max([k for k in range(len(rock)) if rock[k][j] != '.']) + left_down_corner[0]
print(j+left_down_corner[1], left_down_corner[0], 1+min([k for k in range(len(rock)) if rock[k][j] != '.']))
if heights[j+left_down_corner[1]] > left_down_corner[0] + len(rock)-(min([k for k in range(len(rock)) if rock[k][j] != '.'])):
print(heights[j+left_down_corner[1]], left_down_corner[0] + len(rock)-(min([k for k in range(len(rock)) if rock[k][j] != '.'])), "Error!")
print_puzzle(heights, rock, left_down_corner)
print(heights)
print(left_down_corner)
print(len(rock))
print(min([k for k in range(len(rock)) if rock[k][j] != '.']))
raise Exception
heights[j+left_down_corner[1]] = left_down_corner[0] + len(rock)-(min([k for k in range(len(rock)) if rock[k][j] != '.']))
print("## ignore rock:")
print_puzzle(heights, rock, left_down_corner)
return max(heights)
def part2(sample): def part2(sample):
"""Partie 2 du défi""" """Partie 2 du défi"""

View File

@ -3,6 +3,7 @@
Jour 08 du défi Advent Of Code pour l'année 2023 Jour 08 du défi Advent Of Code pour l'année 2023
""" """
import os import os
import math
def read_sample(): def read_sample():
"""récupère les entrées depuis le fichier texte correspondant""" """récupère les entrées depuis le fichier texte correspondant"""
@ -12,19 +13,6 @@ def read_sample():
sample = [ i for i in sample if i != '' ] sample = [ i for i in sample if i != '' ]
return sample return sample
def gcd(a, b):
while b:
a, b = b, a % b
return a
def lcm(a, b):
return (a * b) // gcd(a, b)
def lcm_of_list(numbers):
result = 1
for num in numbers:
result = lcm(result, num)
return result
def parse_sample(sample): def parse_sample(sample):
instructions = sample[0] instructions = sample[0]
@ -34,6 +22,7 @@ def parse_sample(sample):
} }
return instructions, mappings return instructions, mappings
def number_steps(instructions, mappings, pos, untilZZZ=True): def number_steps(instructions, mappings, pos, untilZZZ=True):
step = 0 step = 0
instr = { instr = {
@ -44,6 +33,8 @@ def number_steps(instructions, mappings, pos, untilZZZ=True):
step += 1 step += 1
return step return step
def part1(sample): def part1(sample):
"""Partie 1 du défi""" """Partie 1 du défi"""
instructions, mappings = parse_sample(sample) instructions, mappings = parse_sample(sample)
@ -54,7 +45,7 @@ def part2(sample):
instructions, mappings = parse_sample(sample) instructions, mappings = parse_sample(sample)
a_ending = [i for i in mappings.keys() if i[-1] == 'A'] a_ending = [i for i in mappings.keys() if i[-1] == 'A']
steps = [number_steps(instructions, mappings, i, untilZZZ=False) for i in a_ending] steps = [number_steps(instructions, mappings, i, untilZZZ=False) for i in a_ending]
return lcm_of_list(steps) return math.lcm(*steps)
def main(): def main():

View File

@ -3,8 +3,9 @@
Jour 12 du défi Advent Of Code pour l'année 2023 Jour 12 du défi Advent Of Code pour l'année 2023
""" """
import os import os
from functools import cache, wraps from functools import cache
from time import time
from aoc_utils import decorators
def read_sample(): def read_sample():
"""récupère les entrées depuis le fichier texte correspondant""" """récupère les entrées depuis le fichier texte correspondant"""
@ -24,18 +25,6 @@ def parse_sample(sample):
return springs, counts return springs, counts
def timing(f):
@wraps(f)
def wrap(*args, **kw):
ts = time()
result = f(*args, **kw)
te = time()
print('> %s: %2.4f sec' % \
(f.__name__, te-ts))
return result
return wrap
def nb_poss(spring, count): def nb_poss(spring, count):
def check_new_elem(elem, size, count): def check_new_elem(elem, size, count):
if size > len(count): if size > len(count):
@ -114,7 +103,7 @@ def unfold(springs, counts):
counts[i] = counts[i]*5 counts[i] = counts[i]*5
@timing @decorators.timeit
def part1(sample): def part1(sample):
"""Partie 1 du défi""" """Partie 1 du défi"""
springs, counts = parse_sample(sample) springs, counts = parse_sample(sample)
@ -125,7 +114,7 @@ def part1(sample):
return sum(possibilities) return sum(possibilities)
@timing @decorators.timeit
def part2(sample): def part2(sample):
"""Partie 2 du défi""" """Partie 2 du défi"""
springs, counts = parse_sample(sample) springs, counts = parse_sample(sample)

View File

@ -4,6 +4,8 @@ Jour 18 du défi Advent Of Code pour l'année 2023
""" """
import os import os
from aoc_utils import snippets
directions = { directions = {
"R": (0, 1), "R": (0, 1),
"L": (0, -1), "L": (0, -1),
@ -107,29 +109,6 @@ def print_terrain(terrain):
print() print()
def border(data):
return(sum((i[1] for i in data)))
# https://www.tutorialspoint.com/program-to-find-area-of-a-polygon-in-python
def getInfo(x1, y1, x2, y2):
return x1*y2 - y1*x2
def solve(data):
points = [t[0] for t in data]
N = len(points)
firstx, firsty = points[0]
prevx, prevy = firstx, firsty
res = 0
for i in range(1, N):
nextx, nexty = points[i]
res = res + getInfo(prevx,prevy,nextx,nexty)
prevx = nextx
prevy = nexty
res = res + getInfo(prevx,prevy,firstx,firsty)
return abs(res)/2.0
def part1(sample): def part1(sample):
"""Partie 1 du défi""" """Partie 1 du défi"""
data = parse_sample(sample, new_method=False) data = parse_sample(sample, new_method=False)
@ -140,7 +119,8 @@ def part2(sample):
"""Partie 2 du défi""" """Partie 2 du défi"""
data = parse_sample(sample, new_method=True) data = parse_sample(sample, new_method=True)
terrain = dig(data, only_edges=True) terrain = dig(data, only_edges=True)
return int(solve(terrain)+border(data)//2 +1) points = [i[0] for i in terrain]
return snippets.area(points)
def main(): def main():

View File

@ -3,7 +3,8 @@
Jour 21 du défi Advent Of Code pour l'année 2023 Jour 21 du défi Advent Of Code pour l'année 2023
""" """
import os import os
from aoc_utils.decorators import timeit
from aoc_utils import decorators, snippets
def read_sample(): def read_sample():
"""récupère les entrées depuis le fichier texte correspondant""" """récupère les entrées depuis le fichier texte correspondant"""
@ -62,24 +63,12 @@ def do_steps(sample, steps=26501365, ext=False):
return pos_ts return pos_ts
def lagrange_interpolation(points, x0): @decorators.timeit
result = 0
for i in range(len(points)):
temp = points[i][1]
for j in range(len(points)):
if j != i:
temp *= (x0 - points[j][0]) / (points[i][0] - points[j][0])
result += temp
return int(result)
@timeit
def part1(sample): def part1(sample):
"""Partie 1 du défi""" """Partie 1 du défi"""
return len(do_steps(sample, steps=64, ext=False)) return len(do_steps(sample, steps=64, ext=False))
@timeit @decorators.timeit
def part2(sample): def part2(sample):
"""Partie 2 du défi""" """Partie 2 du défi"""
def challenge(steps): def challenge(steps):
@ -93,7 +82,7 @@ def part2(sample):
(half_size+size, challenge(half_size+size)), (half_size+size, challenge(half_size+size)),
(half_size+2*size, challenge(half_size+2*size)) (half_size+2*size, challenge(half_size+2*size))
] ]
return lagrange_interpolation(points, 26501365) return snippets.lagrange_interpolation(points, 26501365)

View File

@ -6,6 +6,8 @@ import os
import string import string
import random import random
from aoc_utils.intervals import Interval
def read_sample(): def read_sample():
"""récupère les entrées depuis le fichier texte correspondant""" """récupère les entrées depuis le fichier texte correspondant"""
filename = os.path.join(os.path.dirname(__file__ ), "inputs", "day22.txt") filename = os.path.join(os.path.dirname(__file__ ), "inputs", "day22.txt")
@ -22,37 +24,25 @@ class Brick:
coords = text.split("~") coords = text.split("~")
coords = (coords[0].split(','), coords[1].split(',')) coords = (coords[0].split(','), coords[1].split(','))
self.x0 = int(coords[0][0]) self.x = Interval(int(coords[0][0]), int(coords[1][0]))
self.y0 = int(coords[0][1]) self.y = Interval(int(coords[0][1]), int(coords[1][1]))
self.z0 = int(coords[0][2]) self.z = Interval(int(coords[0][2]), int(coords[1][2]))
self.x1 = int(coords[1][0])
self.y1 = int(coords[1][1])
self.z1 = int(coords[1][2])
self.label = random.choice(string.ascii_letters) self.label = random.choice(string.ascii_letters)
self.text = text self.text = text
def __repr__(self): def __repr__(self):
return f"[{self.label}]{self.x0},{self.y0},{self.z0}~{self.x1},{self.y1},{self.z1}" return f"[{self.label}]{self.x.low},{self.y.low},{self.z.low}~{self.x.up},{self.y.up},{self.z.up}"
#return self.text+f"[{self.label}] [h:{self.z0}]" #return self.text+f"[{self.label}] [h:{self.z.low}]"
def supported_by(self, brick): def supported_by(self, brick):
def intersect(interval1, interval2): if brick.z.up == self.z.low-1:
if interval2[0] > interval1[1] or interval1[0] > interval2[1]: if self.x.intersect(brick.x) and self.y.intersect(brick.y):
return None
new_min = max(interval1[0], interval2[0])
new_max = min(interval1[1], interval2[1])
return [new_min, new_max]
if brick.z1 == self.z0-1:
if intersect([self.x0, self.x1], [brick.x0, brick.x1]) is not None \
and intersect([self.y0, self.y1], [brick.y0, brick.y1]) is not None:
return True return True
return False return False
def can_fall(self, bricks): def can_fall(self, bricks):
if self.z0 == 1: if self.z.low == 1:
return False return False
for brick in bricks: for brick in bricks:
if brick != self and self.supported_by(brick): if brick != self and self.supported_by(brick):
@ -61,27 +51,20 @@ class Brick:
def copy(self): def copy(self):
# Assuming your class has a constructor that accepts the same parameters # Assuming your class has a constructor that accepts the same parameters
copie = Brick(f"{self.x0},{self.y0},{self.z0}~{self.x1},{self.y1},{self.z1}") copie = Brick(f"{self.x.low},{self.y.low},{self.z.low}~{self.x.up},{self.y.up},{self.z.up}")
copie.label = self.label copie.label = self.label
return copie return copie
def intersect(interval1, interval2):
if interval2[0] > interval1[1] or interval1[0] > interval2[1]:
return None
new_min = max(interval1[0], interval2[0])
new_max = min(interval1[1], interval2[1])
return [new_min, new_max]
def print_pile(bricks): def print_pile(bricks):
def occupied(x, z): def occupied(x, z):
for b in bricks: for b in bricks:
if b.x0 <= x and x <= b.x1 and b.z0 <= z and z <= b.z1: if b.x.low <= x and x <= b.x.up and b.z.low <= z and z <= b.z.up:
return b return b
return False return False
for z in reversed(range(max((b.z1 for b in bricks))+1)): for z in reversed(range(max((b.z.up for b in bricks))+1)):
for x in range(max((b.x1 for b in bricks))+1): for x in range(max((b.x.up for b in bricks))+1):
occ = occupied(x, z) occ = occupied(x, z)
if occ: if occ:
print(occ.label, end="") print(occ.label, end="")
@ -93,7 +76,7 @@ def print_pile(bricks):
def fall(bricks, count="z"): def fall(bricks, count="z"):
fallen = 0 fallen = 0
has_fallen = False has_fallen = False
bricks.sort(key=lambda b: b.z1) bricks.sort(key=lambda b: b.z.up)
for brick in bricks: for brick in bricks:
has_fallen = False has_fallen = False
while brick.can_fall(bricks): while brick.can_fall(bricks):
@ -101,8 +84,8 @@ def fall(bricks, count="z"):
fallen += 1 fallen += 1
has_fallen = True has_fallen = True
brick.z0 -= 1 brick.z.low -= 1
brick.z1 -= 1 brick.z.up -= 1
if count != "z" and has_fallen: if count != "z" and has_fallen:
fallen += 1 fallen += 1

92
2023/day24.py Executable file
View File

@ -0,0 +1,92 @@
#!/usr/bin/python3
"""
Jour 24 du défi Advent Of Code pour l'année 2023
"""
import os
import z3
def read_sample():
"""récupère les entrées depuis le fichier texte correspondant"""
filename = os.path.join(os.path.dirname(__file__ ), "inputs", "day24.txt")
with open(filename, 'r') as f:
sample = f.read().split('\n')
sample = [ i for i in sample if i != '' ]
return sample
class Hailstone:
def __init__(self, text):
self.px = int(text.split(" @ ")[0].split(", ")[0])
self.py = int(text.split(" @ ")[0].split(", ")[1])
self.pz = int(text.split(" @ ")[0].split(", ")[2])
self.dx = int(text.split(" @ ")[1].split(", ")[0])
self.dy = int(text.split(" @ ")[1].split(", ")[1])
self.dz = int(text.split(" @ ")[1].split(", ")[2])
def __repr__(self):
return f"{self.px}, {self.py}, {self.pz} @ {self.dx}, {self.dy}, {self.dz}"
def intersection(h1, h2):
try:
# ax+b
# cx+d
b, a = h1.py - (h1.px/h1.dx)*h1.dy, h1.dy/h1.dx
d, c = h2.py - (h2.px/h2.dx)*h2.dy, h2.dy/h2.dx
x = (d - b) / (a - c)
y = a*x+b
if (x-h1.px)/h1.dx < 0:
return None
if (x-h2.px)/h2.dx < 0:
return None
except ZeroDivisionError:
return None
return x, y
def part1(sample, left=200000000000000, right=400000000000000):
"""Partie 1 du défi"""
hailstones = [Hailstone(i) for i in sample]
count = 0
for i in range(len(hailstones)):
for j in range(i):
res = intersection(hailstones[i], hailstones[j])
if res is not None:
x, y = res
if left <= x and x <= right and left <= y and y <= right:
count += 1
return count
def part2(sample):
"""Partie 2 du défi"""
hailstones = [Hailstone(i) for i in sample]
px, py, pz, dx, dy, dz = z3.Ints("px py pz dx dy dz")
collision = [z3.Int("t"+str(i)) for i in range(len(hailstones))]
solver = z3.Solver()
for i in range(len(hailstones)):
h = hailstones[i]
solver.add(px + dx*collision[i] == h.px + h.dx*collision[i])
solver.add(py + dy*collision[i] == h.py + h.dy*collision[i])
solver.add(pz + dz*collision[i] == h.pz + h.dz*collision[i])
solver.check()
return solver.model().evaluate(px + py + pz)
def main():
"""Fonction principale"""
sample = read_sample()
print(f"part1: {part1(sample)}")
print(f"part2: {part2(sample)}")
if __name__ == "__main__":
main()

31
aoc_utils/intervals.py Normal file
View File

@ -0,0 +1,31 @@
class Interval:
"""Définit des intervalles de $\\mathbb{R}$"""
def __init__(self, a: float, b: float) -> None:
assert a <= b
self.low = a
self.up = b
def __repr__(self) -> str:
if self.up == self.low:
return '{'+str(self.up)+'}'
return f"[{self.low}, {self.up}]"
def __len__(self) -> float:
return self.up - self.low
def __contains__(self, item: float) -> bool:
return self.low <= item and item <= self.up
def __eq__(self, interval):
return self.low == interval.low and self.up == interval.up
def intersect(self, interval) -> bool:
return not (interval.low > self.up or self.low > interval.up)
def intersection(self, interval):
if interval.low > self.up or self.low > interval.up:
return None
new_min = max(self.low, interval.low)
new_max = min(self.up, interval.up)
return Interval(new_min, new_max)

48
aoc_utils/snippets.py Normal file
View File

@ -0,0 +1,48 @@
from typing import TypeVar
Nb = TypeVar("Nb", int, float)
def lagrange_interpolation(points: list[tuple[Nb, Nb]], x0: Nb) -> int:
result = 0
for i in range(len(points)):
temp = points[i][1]
for j in range(len(points)):
if j != i:
temp *= (x0 - points[j][0]) / (points[i][0] - points[j][0])
result += temp
return int(result)
def area(points: list[tuple[Nb, Nb]], count_border: bool=True) -> int:
def distance(p1: tuple[Nb, Nb], p2: tuple[Nb, Nb]) -> float:
return abs(p1[0]-p2[0]) + abs(p1[1]-p2[1])
def get_info(x1: Nb, y1: Nb, x2: Nb, y2: Nb) -> Nb:
return x1*y2 - y1*x2
def inner_area() -> float:
first_x, first_y = points[0]
prev_x, prev_y = first_x, first_y
res = 0
for i in range(len(points)-1):
next_x, next_y = points[i+1]
res = res + get_info(prev_x, prev_y, next_x, next_y)
prev_x = next_x
prev_y = next_y
res = res + get_info(prev_x, prev_y, first_x, first_y)
return abs(res)/2.0
def border() -> float:
distances = [distance(points[i], points[i+1]) for i in range(len(points)-1)]
distances.append(distance(points[-1], points[0]))
return sum(distances)
if count_border:
return int(inner_area()+border()//2 +1)
return int(inner_area())