From 2fda876df336ccfb638b0237ab266cc2ed682c9c Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Tue, 28 Jan 2025 16:52:27 +0100 Subject: [PATCH] added ex5 --- src/Ex4.py | 35 ++++++++- src/Ex5.py | 190 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_ex5.py | 15 +++- 3 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 src/Ex5.py diff --git a/src/Ex4.py b/src/Ex4.py index eeb59df..c96bd79 100644 --- a/src/Ex4.py +++ b/src/Ex4.py @@ -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 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) -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 le calcul récursif puisque cela évite de calculer les états partagés (sous-branches 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] = {} @@ -37,6 +41,35 @@ def computeNbrOfDifferentSequences(N: int, C: int, H: int) -> int: 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__': print(computeNbrOfDifferentSequences(1, 6, 3) == 1) print(computeNbrOfDifferentSequences(2, 6, 7) == 6) + print(bonus()) diff --git a/src/Ex5.py b/src/Ex5.py new file mode 100644 index 0000000..623ec0c --- /dev/null +++ b/src/Ex5.py @@ -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) diff --git a/tests/test_ex5.py b/tests/test_ex5.py index b24a888..11f588c 100644 --- a/tests/test_ex5.py +++ b/tests/test_ex5.py @@ -1,8 +1,21 @@ import unittest +from Ex5 import playMinitrisFastAndWell + + class MyTestCase(unittest.TestCase): 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__': unittest.main()