◂ 12 décembre 2024 ▸
Garden Groups : Trouver des zones et calculer l'aire, le périmètre et le nombre de côtés.
UUUKKKKZZZZ …
UUKKKKKZZZZ …
UUKKKKKKKZZ …
⋮
- code.py
- code_visualisation.py
- find_4_colors_coloring.py
- colorByLetter.png
- four_colors_plain.png
- multicolors_borders.png
- multicolors_plain.png
- four_colors_terminal_borders.png
from itertools import combinations
with open("input.txt", 'r', encoding='utf-8') as f:
grid = [line[:-1] for line in f.readlines()]
W = len(grid[0])
H = len(grid)
def find_region_borders(x, y, region_ids, current_region_id) -> (int, int, int):
region = set()
borders = set()
region_letter = grid[y][x]
to_do = { (x,y), }
DIRECTIONS = ((1,0), (0,1), (-1,0), (0,-1))
while to_do:
x, y = to_do.pop()
region.add((x,y))
region_ids[y][x] = current_region_id
for dx, dy in DIRECTIONS:
nx, ny = x+dx, y+dy
if (nx,ny) not in region: # not already processed
if (0 <= nx < W) and (0 <= ny < H) and (grid[ny][nx] == region_letter) :
to_do.add((nx,ny))
else:
borders.add( ((x,y), (dx,dy)) )
return borders
region_ids = [W*[None] for i in range(H)]
current_region_id = 0
borders = []
for y in range(H):
for x in range(W):
if not region_ids[y][x]: # not already processed
current_region_id += 1
borders.append(find_region_borders(x, y, region_ids, current_region_id))
edges = dict( ((i, set())) for i in range(1, len(borders) + 1) )
for region_borders in borders:
for (x,y),(dx,dy) in region_borders:
if 0 <= x+dx < W and 0 <= y+dy < H:
edges[region_ids[y][x]].add(region_ids[y+dy][x+dx])
NB_COLORS = 4
ENLEVES = set() # id des régions prises en compte qu'à la fin.
edges_copy = dict((k,v.copy()) for k,v in edges.items())
# enlever les noeuds avec peu de voisins, car on pourra toujours les colorer plus tard
id = True
while id:
id = next((id for id,voisins in edges_copy.items() if len(voisins) < NB_COLORS), None)
if id is not None:
ENLEVES.add(id)
for id_voisin in edges_copy.pop(id):
edges_copy[id_voisin].remove(id)
COULEURS = set(range(NB_COLORS))
couleur_region = dict()
couls_possibles = dict()
for id in edges:
couls_possibles[id] = set(COULEURS)
ids_by_degrees = [id for _,id in sorted( ((len(edges[id]), id) for id in edges.keys()), reverse=True)]
def find_clique(ids, edges, clique_size):
for id in ids:
if id in ENLEVES:
continue
for subset_voisins in combinations(edges[id], clique_size - 1):
if all(subset_voisins[i2] in edges[subset_voisins[i1]] for i1 in range(len(subset_voisins)-1) for i2 in range(i1+1, len(subset_voisins))):
return (id,) + subset_voisins
return None
for clique_size in range(NB_COLORS, 1, -1):
clique = find_clique(ids_by_degrees, edges_copy, clique_size)
if clique:
break
else:
print("No clique size", clique_size)
print("Clique:", clique)
for i, id in enumerate(clique):
couleur_region[id] = i
couls_possibles.pop(id)
ids_by_degrees.remove(id)
for id in couls_possibles.keys():
couls_possibles[id].difference_update( couleur_region[id_clique] for id_clique in edges[id].intersection(clique) )
def get_coloration(couleur_region, couls_possibles, edges):
id = next( ( id for id, coul_poss in couls_possibles.items() if len(coul_poss) <= 1) , None)
while id is not None:
coul_poss = couls_possibles.pop(id)
if len(coul_poss) == 0:
return None
coul = coul_poss.pop()
couleur_region[id] = coul
for id2 in edges[id]:
if id2 in couls_possibles:
couls_possibles[id2].discard(coul)
id = next( ( id for id, coul_poss in couls_possibles.items() if len(coul_poss) <= 1) , None)
if len(couls_possibles) == 0:
print("YOUHOUHOU ! 0")
print(couleur_region)
print("YOUHOUHOU ! 2")
exit()
id,_ = min(couls_possibles.items(), key=lambda t:(t[0] in ENLEVES, len(t[1])))
couleur_region_copy = dict(couleur_region)
for coul in couls_possibles.pop(id):
couleur_region_copy[id] = coul
couls_poss_copy = dict((k,v.copy()) for k,v in couls_possibles.items())
for id2 in edges[id]:
if id2 in couls_poss_copy:
couls_poss_copy[id2].discard(coul)
get_coloration(couleur_region_copy, couls_poss_copy, edges)
get_coloration(couleur_region, couls_possibles, edges)