added ex5
Some checks failed
Python unit tests / unittests (push) Failing after 5s

This commit is contained in:
Louis Heredero 2025-01-28 16:52:27 +01:00
parent 68ccc3c183
commit 2fda876df3
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
3 changed files with 238 additions and 2 deletions

View File

@ -8,10 +8,14 @@ Une approche pour résoudre ce problème grâce à la programmation dynamique co
représente une séquence, et chaque arête un coup supplémentaire représente une séquence, et chaque arête un coup supplémentaire
On peut alors parcourir l'arbre en profondeur (DFS) jusqu'à trouver une séquence On peut alors parcourir l'arbre en profondeur (DFS) jusqu'à trouver une séquence
de longueur N et de valeur H (en élaguant les branches dépassant H ou N) de longueur N et de valeur H (en élaguant les branches dépassant H ou N)
On peut également mémoiser un certain nombre de valeurs : en effet, pour une même On peut également mémoïser un certain nombre de valeurs : en effet, pour une même
combinaison N, C, H, le résultat sera toujours le même. Cela optimise donc grandement combinaison N, C, H, le résultat sera toujours le même. Cela optimise donc grandement
le calcul récursif puisque cela évite de calculer les états partagés (sous-branches le calcul récursif puisque cela évite de calculer les états partagés (sous-branches
identiques) identiques)
Bonus :
Nous cherchons la valeur N la plus petite telle que:
`computeNbrOfDifferentSequences(N, 5, 100) > 2^16`
""" """
mem: dict[tuple[int, int, int], int] = {} mem: dict[tuple[int, int, int], int] = {}
@ -37,6 +41,35 @@ def computeNbrOfDifferentSequences(N: int, C: int, H: int) -> int:
return total return total
def bonus():
# Finding N2
N1 = 100
N2 = 1_000_000_000
C = 5
H = 100
lim = 2 ** 16
while computeNbrOfDifferentSequences(N2, C, H) <= lim:
N2 *= 2
print(f"*= 2 -> {N2}")
print(f"{N2=}")
# Finding N
while N2 - N1 > 1:
Nm = (N1 + N2) // 2
value = computeNbrOfDifferentSequences(Nm, C, H)
if value == lim:
return Nm
if value < lim:
N1 = Nm
else:
N2 = Nm
return N2
if __name__ == '__main__': if __name__ == '__main__':
print(computeNbrOfDifferentSequences(1, 6, 3) == 1) print(computeNbrOfDifferentSequences(1, 6, 3) == 1)
print(computeNbrOfDifferentSequences(2, 6, 7) == 6) print(computeNbrOfDifferentSequences(2, 6, 7) == 6)
print(bonus())

190
src/Ex5.py Normal file
View File

@ -0,0 +1,190 @@
"""
Nom/Prénom: Heredero/Louis
"""
from typing import Optional
# UTILITY FUNCTIONS
# Those functions are provided as helpers, you do not have to use them
# The displayBoardSequence function will be used to visualize the results of your algorithms during the evaluation
def printPiece(piece:tuple[tuple[int,int], tuple[int,int]]) -> None:
print(piece[0][1], piece[1][1])
print(piece[0][0], piece[1][0])
def printBoard(board:list[list[int, int]]) -> None:
if len(board) <= 0 or len(board[0]) <= 0:
print("Empty board -> skipping printing")
return None
for i in range(len(board)-1):
if len(board[i]) != len(board[i+1]):
print("Board is not a rectangle --> skipping printing")
return None
for y in range(len(board[0])-1, -1, -1):
line = ""
for x in range(len(board)):
line += str(board[x][y],)
print(line)
def placeAt(loc, board, piece):
for x in range(2):
for y in range(2):
if piece[x][y] == 0:
continue
board[loc[0] + x][loc[1] + y] = piece[x][y]
def displayBoardSequence(board_width:int, board_height:int, pieces:list[tuple[tuple[int,int], tuple[int,int]]], placements:list[list[int, int]]):
"""Display a sequence of move on the board by placing the blocs at the given locations
==> /!\ This is an unsafe function that simply place the 1 ones of the given blocs at the given placements, it will crash while trying to place blocs outside the board
==> /!\ it does not check the validity of the placement
=> It's main use is for debugging
"""
board = [None] * board_width
for i in range(board_width):
board[i] = [0] * board_height
for i in range(len(pieces)):
print( "----", i, "----")
print("Piece to place")
printPiece(pieces[i])
print("Board state after placing piece @ "+ str(placements[i]))
placeAt(placements[i], board, pieces[i])
printBoard(board)
"""
Explications:
Une approche possible consiste à représenter la partie sous la forme d'un arbre
dans lequel les nœuds sont les états de la grille et les arêtes sont les coups
possibles (i.e. placement d'une pièce)
Ainsi, l'état initial serait celui de la grille vide, et chaque "niveau" de l'arbre
représenterait une pièce différente pouvant être posée
On peut ensuite parcourir l'arbre en largeur (BFS) ce qui nous garantit de trouver une solution optimale en premier.
On peut également le parcourir en profondeur (DFS) jusqu'à trouver une solution, puis en parcourant le reste de l'arbre
à la recherche d'une meilleure solution. Cela permettrait également d'appliquer
de la mémoïsation (principe de programmation dynamique) afin d'optimiser la recherche
Afin d'accélérer la recherche nous pouvons appliquer les optimisations suivantes:
- élagage des branches inintéressantes : dès qu'une pièce laisse un espace vide sous elle, il devient impossible de le remplir
- tri des branches selon la hauteur de la pièce une fois posée : prioriser les branches plaçant les pièces le plus bas possible
- combinaisons de pièces en macro-blocs : combiner les pièces pouvant s'assembler (p.ex. coin et carré)
"""
def find_y(board: list[list[int]], height: int, piece: tuple[tuple[int,int], tuple[int,int]], x: int) -> int:
y_max = height - 2
if piece[0][1] == 0 and piece[1][1] == 0:
y_max = height - 1
y_min = 0
if piece[0][0] == 0 and piece[1][0] == 0:
y_min = -1
lowest = 0
for y in range(y_max, y_min - 1, -1):
valid = True
for dy in range(2):
for dx in range(2):
if piece[dx][dy] == 1 and board[x + dx][y + dy] == 1:
valid = False
break
if not valid:
break
if valid:
lowest = y
else:
break
return lowest
def copy_board(board: list[list[int]]) -> list[list[int]]:
new_board: list[list[int]] = [
row.copy()
for row in board
]
return new_board
def playMinitris(
board_width: int,
board_height: int,
board: list[list[int]],
pieces: list[tuple[tuple[int,int], tuple[int,int]]],
path: list[tuple[int, int]]
) -> Optional[list[tuple[int, int]]]:
if len(pieces) == 0:
for y in range(board_height):
row = []
for x in range(board_width):
row.append(board[x][y])
if row != [row[0]] * board_width:
return None
return path
first_piece: tuple[tuple[int, int], tuple[int, int]] = pieces[0]
rest: list[tuple[tuple[int, int], tuple[int, int]]] = pieces[1:]
x_min = 0
if first_piece[0][0] == 0 and first_piece[0][1] == 0:
x_min = -1
x_max = board_width - 2
if first_piece[1][0] == 0 and first_piece[1][1] == 0:
x_max = board_width - 1
for x in range(x_min, x_max + 1):
y = find_y(board, board_height, first_piece, x)
new_board = copy_board(board)
pos = (x, y)
path2 = path + [pos]
placeAt(pos, new_board, first_piece)
# If space below piece
if first_piece[0][1] == 1 and first_piece[1][1] and first_piece[0][0] != first_piece[1][0]:
if new_board[x][y] == 0 or new_board[x + 1][y] == 0:
return None
res: Optional[list[tuple[int, int]]] = playMinitris(board_width, board_height, new_board, rest, path2)
if res is not None:
return res
return None
# This is the function to fill
def playMinitrisFastAndWell(
board_width: int,
board_height: int,
pieces: list[tuple[tuple[int,int], tuple[int,int]]]
) -> list[list[int, int]]:
board: list[list[int]] = [
[0] * board_height
for _ in range(board_width)
]
result: Optional[list[tuple[int, int]]] = playMinitris(
board_width,
board_height,
board,
pieces,
[]
)
if result is None:
return []
return list(map(list, result))
if __name__ == '__main__':
displayBoardSequence(3, 4, [((1, 0), (0, 0)), ((1, 0), (0, 0)), ((1, 0), (0, 0))], [(0, 0), (1, 0), (2, 0)])
displayBoardSequence(3, 4, [((1, 0), (0, 0)), ((1, 1), (0, 1)), ((1, 1), (0, 0))], [(1, 0), (0, 0), (2, 0)])
w = 3
h = 4
pieces = [((1, 0), (0, 0)), ((1, 0), (0, 0)), ((1, 0), (0, 0))]
seq = playMinitrisFastAndWell(w, h, pieces)
displayBoardSequence(w, h, pieces, seq)

View File

@ -1,8 +1,21 @@
import unittest import unittest
from Ex5 import playMinitrisFastAndWell
class MyTestCase(unittest.TestCase): class MyTestCase(unittest.TestCase):
def test_simple1(self): def test_simple1(self):
pass self.assertEqual(
playMinitrisFastAndWell(3, 4, [((1, 0), (0, 0)), ((1, 0), (0, 0)), ((1, 0), (0, 0))]),
[[0, 0], [1, 0], [2, 0]]
)
def test_simple2(self):
self.assertEqual(
playMinitrisFastAndWell(3, 4, [((1, 0), (0, 0)), ((1, 1), (0, 1)), ((1, 1), (0, 0))]),
[[1, 0], [0, 0], [2, 0]]
)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()