This commit is contained in:
parent
c7c36f7717
commit
307c46d86a
208
src/ex4_furniture.py
Normal file
208
src/ex4_furniture.py
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
"""
|
||||||
|
Question 4 - Le beau, la bête et les meubles mouvants
|
||||||
|
|
||||||
|
But : calculer le nombre minimum de déplacements (et lesquels) pour passer d'un
|
||||||
|
agencement de meubles à un autre
|
||||||
|
|
||||||
|
Approche :
|
||||||
|
|
||||||
|
Nous pouvons représenter la situation par un graphe d'états, dans lequel chaque
|
||||||
|
nœud correspond à un agencement de meuble, et chaque arête à un déplacement.
|
||||||
|
Nous pouvons ensuite partir de la situation initiale et explore cet arbre
|
||||||
|
(par exemple en BFS) jusqu'à trouver la disposition finale. Afin d'optimiser un
|
||||||
|
peu le BFS naïf, nous pouvons trier les nœuds selon la somme des distances entre
|
||||||
|
la position actuelle de chaque meuble et sa position finale.
|
||||||
|
|
||||||
|
Par la nature du BFS, la première solution que nous trouverons sera la moins
|
||||||
|
profonde, c'est-à-dire celle comportant le moins de déplacements.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
OFFSETS = [
|
||||||
|
(0, -1),
|
||||||
|
(-1, 0),
|
||||||
|
(0, 1),
|
||||||
|
(1, 0),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Furniture:
|
||||||
|
def __init__(self, id: int, pos: tuple[int, int], tiles: list[tuple[int, int]]):
|
||||||
|
self.id: int = id
|
||||||
|
self.pos: tuple[int, int] = pos
|
||||||
|
self.tiles: list[tuple[int, int]] = tiles
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_list(id: int, tiles: list[tuple[int, int]]) -> Furniture:
|
||||||
|
x = [tile[0] for tile in tiles]
|
||||||
|
y = [tile[1] for tile in tiles]
|
||||||
|
ox = min(x)
|
||||||
|
oy = min(y)
|
||||||
|
tiles2 = [(tile[0] - ox, tile[1] - oy) for tile in tiles]
|
||||||
|
return Furniture(id, (ox, oy), tiles2)
|
||||||
|
|
||||||
|
def move(self, offset: tuple[int, int]) -> Furniture:
|
||||||
|
return Furniture(self.id, (self.pos[0] + offset[0], self.pos[1] + offset[1]), self.tiles)
|
||||||
|
|
||||||
|
def copy(self) -> Furniture:
|
||||||
|
return Furniture(self.id, self.pos, self.tiles)
|
||||||
|
|
||||||
|
def get_tiles(self) -> list[tuple[int, int]]:
|
||||||
|
ox, oy = self.pos
|
||||||
|
return [
|
||||||
|
(ox + dx, oy + dy)
|
||||||
|
for dx, dy in self.tiles
|
||||||
|
]
|
||||||
|
|
||||||
|
def dist(self, f2: Furniture) -> int:
|
||||||
|
return abs(self.pos[0] - f2.pos[0]) + abs(self.pos[1] - f2.pos[1])
|
||||||
|
|
||||||
|
class State:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
#plan: list[list[int]],
|
||||||
|
width: int,
|
||||||
|
height: int,
|
||||||
|
furniture: dict[id, Furniture],
|
||||||
|
parent: Optional[State] = None
|
||||||
|
):
|
||||||
|
#self.plan: list[list[int]] = plan
|
||||||
|
self.furniture: dict[id, Furniture] = furniture
|
||||||
|
self.parent: Optional[State] = parent
|
||||||
|
#self.width: int = len(plan[0])
|
||||||
|
#self.height: int = len(plan)
|
||||||
|
self.width: int = width
|
||||||
|
self.height: int = height
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_list(plan: list[list[int]]) -> State:
|
||||||
|
furniture: dict[int, list[tuple[int, int]]] = {}
|
||||||
|
width = len(plan[0])
|
||||||
|
height = len(plan)
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
tile: int = plan[y][x]
|
||||||
|
if tile != 0:
|
||||||
|
if tile not in furniture:
|
||||||
|
furniture[tile] = []
|
||||||
|
furniture[tile].append((x, y))
|
||||||
|
furniture2: dict[int, Furniture] = {
|
||||||
|
i: Furniture.from_list(i, tiles)
|
||||||
|
for i, tiles in furniture.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
#return State(plan, furniture2)
|
||||||
|
return State(width, height, furniture2)
|
||||||
|
|
||||||
|
def to_list(self) -> list[list[int]]:
|
||||||
|
plan: list[list[int]] = [
|
||||||
|
[0 for _ in range(self.width)]
|
||||||
|
for _ in range(self.height)
|
||||||
|
]
|
||||||
|
for furniture in self.furniture.values():
|
||||||
|
for tx, ty in furniture.get_tiles():
|
||||||
|
plan[ty][tx] = furniture.id
|
||||||
|
|
||||||
|
return plan
|
||||||
|
|
||||||
|
def get_depth(self) -> int:
|
||||||
|
if self.parent is None:
|
||||||
|
return 0
|
||||||
|
return self.parent.get_depth() + 1
|
||||||
|
|
||||||
|
def apply_move(self, id: int, offset: tuple[int, int]) -> Optional[State]:
|
||||||
|
#new_plan: list[list[int]] = [[0] * self.width for _ in range(self.height)]
|
||||||
|
|
||||||
|
"""
|
||||||
|
for y, row in enumerate(self.plan):
|
||||||
|
for x, tile in enumerate(row):
|
||||||
|
if tile == id:
|
||||||
|
x2, y2 = x + offset[0], y + offset[1]
|
||||||
|
if x2 < 0 or x2 >= self.width or y2 < 0 or y2 >= self.height:
|
||||||
|
return None
|
||||||
|
if new_plan[y2][x2] not in (0, id):
|
||||||
|
return None
|
||||||
|
new_plan[y2][x2] = id
|
||||||
|
new_plan[y][x] = 0
|
||||||
|
else:
|
||||||
|
new_plan[y][x] = tile
|
||||||
|
"""
|
||||||
|
plan: list[list[int]] = self.to_list()
|
||||||
|
furn2: Furniture = self.furniture[id].move(offset)
|
||||||
|
for tx, ty in furn2.get_tiles():
|
||||||
|
if tx < 0 or tx >= self.width or ty < 0 or ty >= self.height:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if plan[ty][tx] not in (0, id):
|
||||||
|
return None
|
||||||
|
|
||||||
|
new_furn: dict[int, Furniture] = {
|
||||||
|
i: f.copy()
|
||||||
|
for i, f in self.furniture.items()
|
||||||
|
}
|
||||||
|
new_furn[id] = furn2
|
||||||
|
|
||||||
|
return State(self.width, self.height, new_furn, self)
|
||||||
|
|
||||||
|
def get_children(self) -> list[State]:
|
||||||
|
moves: list[tuple[int, tuple[int, int]]] = self.get_moves()
|
||||||
|
children: list[State] = [
|
||||||
|
self.apply_move(move[0], move[1])
|
||||||
|
for move in moves
|
||||||
|
]
|
||||||
|
children = list(filter(lambda child: child is not None, children))
|
||||||
|
|
||||||
|
return children
|
||||||
|
|
||||||
|
def get_moves(self) -> list[tuple[int, tuple[int, int]]]:
|
||||||
|
moves: list[tuple[int, tuple[int, int]]] = []
|
||||||
|
for id in self.furniture.keys():
|
||||||
|
for offset in OFFSETS:
|
||||||
|
moves.append((id, offset))
|
||||||
|
return moves
|
||||||
|
|
||||||
|
def matches(self, state2: State) -> bool:
|
||||||
|
for id in self.furniture.keys():
|
||||||
|
f1: Furniture = self.furniture[id]
|
||||||
|
f2: Furniture = state2.furniture[id]
|
||||||
|
if f1.pos != f2.pos:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_path_list(self) -> list[list[list[int]]]:
|
||||||
|
path = []
|
||||||
|
if self.parent is not None:
|
||||||
|
path = self.parent.get_path_list()
|
||||||
|
|
||||||
|
return path + [self.to_list()]
|
||||||
|
|
||||||
|
def get_dist(self, state2: State) -> int:
|
||||||
|
total_dist: int = 0
|
||||||
|
for id in self.furniture.keys():
|
||||||
|
f1: Furniture = self.furniture[id]
|
||||||
|
f2: Furniture = state2.furniture[id]
|
||||||
|
total_dist += f1.dist(f2)
|
||||||
|
|
||||||
|
return total_dist
|
||||||
|
|
||||||
|
|
||||||
|
def minimumMoves(current_plan: list[list[int]], target_plan: list[list[int]]) -> list[list[list[int]]]:
|
||||||
|
current_state = State.from_list(current_plan)
|
||||||
|
target_state = State.from_list(target_plan)
|
||||||
|
|
||||||
|
|
||||||
|
states: list[State] = [current_state]
|
||||||
|
while len(states) != 0:
|
||||||
|
new_states: list[State] = []
|
||||||
|
for state in states:
|
||||||
|
if state.matches(target_state):
|
||||||
|
return state.get_path_list()
|
||||||
|
|
||||||
|
children = state.get_children()
|
||||||
|
new_states += children
|
||||||
|
states = sorted(new_states, key=lambda s: s.get_dist(target_state))
|
||||||
|
|
||||||
|
print("Could not find a way to rearrange the room")
|
||||||
|
return []
|
51
tests/test_ex4.py
Normal file
51
tests/test_ex4.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from src.ex4_furniture import minimumMoves
|
||||||
|
|
||||||
|
|
||||||
|
class MyTestCase(unittest.TestCase):
|
||||||
|
def test_simple1(self):
|
||||||
|
current_plan = [
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[2, 0, 1, 0],
|
||||||
|
[0, 0, 1, 0]
|
||||||
|
]
|
||||||
|
target_plan = [
|
||||||
|
[0, 0, 2, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0 ,0]
|
||||||
|
]
|
||||||
|
self.assertEqual(
|
||||||
|
minimumMoves(current_plan, target_plan),
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[2, 0, 1, 0],
|
||||||
|
[0, 0, 1, 0]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[2, 0, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 1, 0]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 2, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 1, 0]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 2, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 1, 0]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 2, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user