Compare commits
No commits in common. "e5ba1cc940c90a83aa0e844674f28d753e8d7fc5" and "12f0be2a5013c92f666dc48809c27a25e9c5000b" have entirely different histories.
e5ba1cc940
...
12f0be2a50
@ -3,8 +3,9 @@
|
|||||||
Jour 16 du défi Advent Of Code pour l'année 2023
|
Jour 16 du défi Advent Of Code pour l'année 2023
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
from functools import cache, wraps
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from aoc_utils.decorators import timeit
|
import time
|
||||||
|
|
||||||
def read_sample() -> list[str]:
|
def read_sample() -> list[str]:
|
||||||
"""récupère les entrées depuis le fichier texte correspondant"""
|
"""récupère les entrées depuis le fichier texte correspondant"""
|
||||||
@ -15,6 +16,18 @@ def read_sample() -> list[str]:
|
|||||||
return sample
|
return sample
|
||||||
|
|
||||||
|
|
||||||
|
def timeit(func):
|
||||||
|
@wraps(func)
|
||||||
|
def timeit_wrapper(*args, **kwargs):
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
total_time = end_time - start_time
|
||||||
|
print(f'Function {func.__name__} Took {total_time:.4f} seconds')
|
||||||
|
return result
|
||||||
|
return timeit_wrapper
|
||||||
|
|
||||||
|
|
||||||
def diff(l1: set, l2: set) -> bool:
|
def diff(l1: set, l2: set) -> bool:
|
||||||
return len(l1 ^ l2) > 0
|
return len(l1 ^ l2) > 0
|
||||||
|
|
||||||
|
@ -5,8 +5,15 @@ Jour 23 du défi Advent Of Code pour l'année 2023
|
|||||||
import os
|
import os
|
||||||
import heapq
|
import heapq
|
||||||
|
|
||||||
from aoc_utils import graph, constants, decorators
|
# Un peu chaotique là
|
||||||
|
dir_ections ={
|
||||||
|
'^': (-1, 0),
|
||||||
|
'v': (1, 0),
|
||||||
|
'<': (0, -1),
|
||||||
|
'>': (0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
directions = [(1, 0), (-1,0), (0, 1), (0, -1)]
|
||||||
|
|
||||||
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"""
|
||||||
@ -55,7 +62,7 @@ def added_elems(pos, npos):
|
|||||||
def find_next(sample, i, j):
|
def find_next(sample, i, j):
|
||||||
"""Pourquoi en double ? je ne suis pas sûr duquel est exécuté donc je touche pas pour le moment"""
|
"""Pourquoi en double ? je ne suis pas sûr duquel est exécuté donc je touche pas pour le moment"""
|
||||||
symb = sample[i][j]
|
symb = sample[i][j]
|
||||||
dirt = constants.arrows_dir[symb]
|
dirt = dir_ections[symb]
|
||||||
i, j = i+dirt[0], j+dirt[1]
|
i, j = i+dirt[0], j+dirt[1]
|
||||||
i, j = i+dirt[0], j+dirt[1]
|
i, j = i+dirt[0], j+dirt[1]
|
||||||
|
|
||||||
@ -64,10 +71,10 @@ def find_next(sample, i, j):
|
|||||||
def compress_voisins(i, j, sample, symbols={'.'}):
|
def compress_voisins(i, j, sample, symbols={'.'}):
|
||||||
"""Prochains voisins en sautant les longs tunnels"""
|
"""Prochains voisins en sautant les longs tunnels"""
|
||||||
def two_dirs(pi, pj):
|
def two_dirs(pi, pj):
|
||||||
return len([(a, b) for a, b in constants.cardinal_dir if valid((pi+a, pj+b), sample) and sample[pi+a][pj+b] in symbols]) <= 2
|
return len([(a, b) for a, b in directions if valid((pi+a, pj+b), sample) and sample[pi+a][pj+b] in symbols]) <= 2
|
||||||
|
|
||||||
v = set()
|
v = set()
|
||||||
for d in constants.cardinal_dir:
|
for d in directions:
|
||||||
pi, pj = i+d[0], j+d[1]
|
pi, pj = i+d[0], j+d[1]
|
||||||
if valid((pi, pj), sample) and sample[pi][pj] in symbols:
|
if valid((pi, pj), sample) and sample[pi][pj] in symbols:
|
||||||
while valid((pi, pj), sample) and sample[pi][pj] in symbols and two_dirs(pi, pj):
|
while valid((pi, pj), sample) and sample[pi][pj] in symbols and two_dirs(pi, pj):
|
||||||
@ -81,7 +88,7 @@ def longest_hike(sample, start, end, part=1):
|
|||||||
def find_next(sample, i, j): # Globalement inutile, je n'avais pas compris la question comme ça initialement
|
def find_next(sample, i, j): # Globalement inutile, je n'avais pas compris la question comme ça initialement
|
||||||
"""Renvoie la position après avoir marché sur i, j"""
|
"""Renvoie la position après avoir marché sur i, j"""
|
||||||
symb = sample[i][j]
|
symb = sample[i][j]
|
||||||
dirt = constants.arrows_dir[symb]
|
dirt = dir_ections[symb]
|
||||||
i, j = i+dirt[0], j+dirt[1]
|
i, j = i+dirt[0], j+dirt[1]
|
||||||
while valid((i, j), sample) and sample[i][j] != '#':
|
while valid((i, j), sample) and sample[i][j] != '#':
|
||||||
i, j = i+dirt[0], j+dirt[1]
|
i, j = i+dirt[0], j+dirt[1]
|
||||||
@ -123,6 +130,7 @@ def longest_hike(sample, start, end, part=1):
|
|||||||
for npos in pre_voisins[pos]:
|
for npos in pre_voisins[pos]:
|
||||||
if npos == end:
|
if npos == end:
|
||||||
to_end.append((dist+abs(npos[0]-pos[0])+abs(npos[1]-pos[1]), [])) #! Faster to return [] but will loose the "good" return path
|
to_end.append((dist+abs(npos[0]-pos[0])+abs(npos[1]-pos[1]), [])) #! Faster to return [] but will loose the "good" return path
|
||||||
|
print("new end:", dist+abs(npos[0]-pos[0])+abs(npos[1]-pos[1]))
|
||||||
current_max = max(current_max, dist+abs(npos[0]-pos[0])+abs(npos[1]-pos[1]))
|
current_max = max(current_max, dist+abs(npos[0]-pos[0])+abs(npos[1]-pos[1]))
|
||||||
|
|
||||||
added = added_elems(pos, npos)
|
added = added_elems(pos, npos)
|
||||||
@ -130,6 +138,8 @@ def longest_hike(sample, start, end, part=1):
|
|||||||
if not_intersect(added, prev) and valid(npos, sample):
|
if not_intersect(added, prev) and valid(npos, sample):
|
||||||
heapq.heappush(priority_queue, (dist+abs(npos[0]-pos[0])+abs(npos[1]-pos[1]), (npos, prev+added)))
|
heapq.heappush(priority_queue, (dist+abs(npos[0]-pos[0])+abs(npos[1]-pos[1]), (npos, prev+added)))
|
||||||
|
|
||||||
|
|
||||||
|
print([i[0] for i in to_end])
|
||||||
return max(to_end)
|
return max(to_end)
|
||||||
|
|
||||||
|
|
||||||
@ -144,84 +154,19 @@ def print_sol(solt, sample):
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
def graph_longest_hike(g, start, end):
|
|
||||||
to_end = set()
|
|
||||||
|
|
||||||
priority_queue = [(0, (start, [start]))]
|
|
||||||
while priority_queue:
|
|
||||||
popped = heapq.heappop(priority_queue)
|
|
||||||
dist, data = popped
|
|
||||||
pos, prev = data
|
|
||||||
|
|
||||||
for voisin, local_dist in g[pos]:
|
|
||||||
if voisin == end:
|
|
||||||
if dist+local_dist not in to_end:
|
|
||||||
print(f"\rMaximum actuel: {dist+local_dist}", end="")
|
|
||||||
to_end.add(dist+local_dist)
|
|
||||||
|
|
||||||
if voisin not in prev:
|
|
||||||
heapq.heappush(priority_queue, (dist+local_dist, (voisin, prev+[voisin])))
|
|
||||||
|
|
||||||
print()
|
|
||||||
return max(to_end)
|
|
||||||
|
|
||||||
def print_sol(solt, sample):
|
|
||||||
"""Afficher une solution"""
|
|
||||||
for i in range(len(sample)):
|
|
||||||
for j in range(len(sample[0])):
|
|
||||||
if (i, j) in solt:
|
|
||||||
print('O', end='')
|
|
||||||
else:
|
|
||||||
print(sample[i][j], end='')
|
|
||||||
print()
|
|
||||||
|
|
||||||
def create_graph(sample, symbols={'.'}):
|
|
||||||
def get_voisins(i, j):
|
|
||||||
potentials = [(i+a, j+b) for a, b in constants.cardinal_dir]
|
|
||||||
v = set()
|
|
||||||
for a, b in potentials:
|
|
||||||
if valid((a, b), sample) and sample[a][b] in symbols:
|
|
||||||
v.add(((a, b), 1))
|
|
||||||
elif valid((a, b), sample) and sample[a][b] in constants.arrows_dir.keys():
|
|
||||||
direction = constants.arrows_dir[sample[a][b]]
|
|
||||||
if valid((a+direction[0], b+direction[1]), sample) and sample[a+direction[0]][b+direction[1]] in symbols:
|
|
||||||
v.add(((a+direction[0], b+direction[1]), 2))
|
|
||||||
|
|
||||||
return {(voisin, cout) for voisin, cout in v if voisin != (i, j)}
|
|
||||||
|
|
||||||
g = graph.Graph()
|
|
||||||
for i in range(len(sample)):
|
|
||||||
for j in range(len(sample[0])):
|
|
||||||
if sample[i][j] in symbols:
|
|
||||||
g.add_node((i, j))
|
|
||||||
|
|
||||||
for node in g:
|
|
||||||
for voisin, cost in get_voisins(*node):
|
|
||||||
g.add_edge(node, voisin, weight=cost)
|
|
||||||
|
|
||||||
return g
|
|
||||||
|
|
||||||
@decorators.timeit
|
|
||||||
def part1(sample):
|
def part1(sample):
|
||||||
"""Partie 1 du défi"""
|
"""Partie 1 du défi"""
|
||||||
# On ne peut pas utiliser la méthode du graphe car
|
|
||||||
# on compresse les graphes non orientés seulement
|
|
||||||
|
|
||||||
# Attention: ne donne pas le bon résultat sur les tests
|
|
||||||
start, end = find_start(sample), find_end(sample)
|
start, end = find_start(sample), find_end(sample)
|
||||||
value, points = longest_hike(sample, start, end)
|
value, points = longest_hike(sample, start, end)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@decorators.timeit
|
|
||||||
def part2(sample):
|
def part2(sample):
|
||||||
"""Partie 2 du défi"""
|
"""Partie 2 du défi"""
|
||||||
# au bout de 1h20, 2378 seulement avec l'ancienne méthode
|
# au bout de 1h20, 2378 seulement.
|
||||||
# 4mn pour tout faire avec un graphe "compressé"
|
|
||||||
g = create_graph(sample, symbols={'.', '<', '>', '^', 'v'})
|
|
||||||
ratio = g.compress()
|
|
||||||
|
|
||||||
start, end = find_start(sample), find_end(sample)
|
start, end = find_start(sample), find_end(sample)
|
||||||
return graph_longest_hike(g, start, end)
|
value, points = longest_hike(sample, start, end, part=2)
|
||||||
|
#print_sol(points, sample)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
from collections.abc import Mapping
|
|
||||||
from typing import TypeVar, Optional, Iterator, Generic
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
class Graph(Mapping, Generic[T]):
|
|
||||||
"""Non oriented graph"""
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self._edges: dict = {}
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "\n".join(
|
|
||||||
(f"{key} -> {self._edges[key]}" for key in self._edges)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __len__(self) -> int:
|
|
||||||
return len(self._edges)
|
|
||||||
|
|
||||||
def __getitem__(self, e) -> list[tuple[T, int]]:
|
|
||||||
return self._edges[e]
|
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[object]:
|
|
||||||
return iter(self._edges)
|
|
||||||
|
|
||||||
def __contains__(self, item: object) -> bool:
|
|
||||||
return item in self._edges.keys()
|
|
||||||
|
|
||||||
def add_node(self, u: T) -> None:
|
|
||||||
if u in self._edges.keys():
|
|
||||||
raise IndexError
|
|
||||||
|
|
||||||
self._edges[u] = set()
|
|
||||||
|
|
||||||
def add_edge(self, source: T, target: T, weight=None) -> None:
|
|
||||||
if source not in self._edges:
|
|
||||||
self.add_node(source)
|
|
||||||
|
|
||||||
if target not in self._edges:
|
|
||||||
self.add_node(target)
|
|
||||||
|
|
||||||
if weight is None:
|
|
||||||
weight = 1
|
|
||||||
|
|
||||||
self._edges[source].add((target, weight))
|
|
||||||
self._edges[target].add((source, weight))
|
|
||||||
|
|
||||||
|
|
||||||
def remove_edges(self, source: T, target: T) -> None:
|
|
||||||
for vertice in frozenset(self._edges[source]):
|
|
||||||
if vertice[0] == target:
|
|
||||||
self._edges[source] -= {vertice}
|
|
||||||
|
|
||||||
for vertice in frozenset(self._edges[target]):
|
|
||||||
if vertice[0] == source:
|
|
||||||
self._edges[target] -= {vertice}
|
|
||||||
|
|
||||||
|
|
||||||
def remove_node(self, u: T) -> None:
|
|
||||||
if u not in self:
|
|
||||||
raise IndexError
|
|
||||||
|
|
||||||
for node in self:
|
|
||||||
self.remove_edges(u, node)
|
|
||||||
|
|
||||||
self._edges.pop(u, None)
|
|
||||||
|
|
||||||
def compress(self) -> None:
|
|
||||||
"""Remove nodes that have only 2 edges to get a minimal representation"""
|
|
||||||
initial_size = len(self)
|
|
||||||
for node in self._edges.copy():
|
|
||||||
if len(self[node]) == 2:
|
|
||||||
weight = sum((edge[1] for edge in self[node]))
|
|
||||||
self.add_edge(list(self[node])[0][0], list(self[node])[1][0], weight=weight)
|
|
||||||
self.remove_node(node)
|
|
||||||
|
|
||||||
return len(self)/initial_size
|
|
||||||
|
|
||||||
|
|
||||||
def bellman_ford(self, source: T) -> \
|
|
||||||
tuple[dict[T, int], dict[T, Optional[T]]]:
|
|
||||||
# Initialize distances and predecessors
|
|
||||||
dist_max = len(self)*max(
|
|
||||||
{max({j[1] for j in i}) for i in self._edges.values()}
|
|
||||||
)
|
|
||||||
distances = {node: dist_max for node in self._edges}
|
|
||||||
predecessors = {
|
|
||||||
node: None for node in self._edges
|
|
||||||
} # ! The only problem is here for the typing system
|
|
||||||
distances[source] = 0
|
|
||||||
|
|
||||||
# Relax edges repeatedly to find the shortest paths
|
|
||||||
for _ in range(len(self._edges) - 1):
|
|
||||||
for u in self._edges:
|
|
||||||
for v, weight in self._edges[u]:
|
|
||||||
if distances[u] + weight < distances[v]:
|
|
||||||
distances[v] = distances[u] + weight
|
|
||||||
predecessors[v] = u
|
|
||||||
|
|
||||||
# Check for negative cycles
|
|
||||||
for u in self._edges:
|
|
||||||
for v, weight in self._edges[u]:
|
|
||||||
if distances[u] + weight < distances[v]:
|
|
||||||
raise ValueError("Graph contains a negative cycle")
|
|
||||||
|
|
||||||
return distances, predecessors
|
|
Loading…
x
Reference in New Issue
Block a user