Correction de Cases inaccessibles

Quelques propositions d'élèves, et à la fin un corrigé du professeur.

Propositions d'élèves

À la suite de chaque proposition de code, un commentaire de correction du professeur. Le cartouche demandé en introduction a été supprimé ici.

Proposition 1

def nb_cases_inaccessibles(nb_lignes: int, nb_colonnes: int, cases: list) -> int:
    """
    Recherche et renvoie le nombre de case inaccesible 
    """
    # 4 directions possibles
    directions = [(1, 0),(0,1),(-1,0),(0,-1)]

    def est_possible(y:int, x:int) -> bool:
        """ Regarde si le déplacement est possible
        """
        return (0 <= y < nb_lignes) and (0 <= x < nb_colonnes) 

    def recherche_chemin(y:int, x:int, direction_y:int, direction_x:int) -> int:
        """ Recherche et renvoie 1 si on peut prendre le chemin sinon 0
        """    
        déplacement_y = y + direction_y 
        déplacement_x  = x + direction_x
        if not(est_possible(déplacement_y, déplacement_x)) and (cases[y][x] < cases[déplacement_y][déplacement_x]):
            return 0
        return 1

    départ = (0,0) 
    def nombre_pas(départ:tuple) -> int:
        """ Renvoie le nombre de case visité de manière récursive """
        # Fonctionne théoriquement mais pas à la pratique car il y a sois disant trop de récursion
        nb_pas = 0
        y,x = départ
        for direction_y, direction_x in directions:
            if recherche_chemin(y,x,direction_y,direction_x) != 0:
                déplacement_x = x + direction_x
                déplacement_y = y + direction_y
                nb_pas += 1 + nombre_pas((déplacement_y,déplacement_x))
                
        return nb_pas
    return nombre_pas(départ)


        
# tests
import doctest
doctest.testmod()

# Entrée
nb_lignes, nb_colonnes = map(int, input().split())
cases = [list(map(int, input().split())) for _ in range(nb_lignes)]

# Sortie
nombre_cases = nb_lignes * nb_colonnes
print(nombre_cases - nb_cases_inaccessibles(nb_lignes, nb_colonnes, cases))

Proposition 2

from collections import deque

# Entrée
nb_lignes, nb_colonnes = map(int, input().split())
cases = [list(map(int, input().split())) for _ in range(nb_lignes)]

dictionnaire_vecteurs = {"N": (-1, 0), "S": (1, 0), "O": (0, -1), "E": (0, 1)}

#J'utilise une `deque` comme implémentation de la file (celle-ci est plus efficace et plus rapide qu'une classe `File` en P.O.O)
file = deque()
enfile = file.append
défile = file.popleft

def est_dans_la_liste(x: int, y: int) -> bool:
    """
    Permet de renvoyer True si les coordonnées `x` et `y` sont dans le domaine de 
    définition de la liste `cases`
    >>> cases = [[1, 6, 3], [5, 9, 6], [7, 2, 0], [5, 4, 6]]
    >>> est_dans_la_liste(1,2)
    True
    >>> est_dans_la_liste(9, 5)
    False
    """
    return ((0 <= x < nb_lignes) and (0 <= y < nb_colonnes))

def est_déplaçable(élément: int, x: int, y: int) -> bool:
    """
    Permet de renvoyer True si la valeur situé aux coordonnées (`x`,`y`) est inférieure ou égale
    à `élément`
    >>> cases = [[1, 6, 3], [5, 9, 6], [7, 2, 0], [5, 4, 6]] 
    >>> est_déplaçable(cases[0][1], 0, 2)
    True
    >>> est_déplaçable(cases[0][0], 0, 1)
    False
    """
    return cases[x][y] <= élément

def donne_les_directions(x: int, y: int) -> list:
    """
    Renvoie la liste des directions où on peut se déplacer à partir des coordonnées(`x`, `y`)
    >>> cases = [[1, 6, 3], [5, 9, 6], [7, 2, 0], [5, 4, 6]] 
    >>> donne_les_directions(0, 0)
    ['S']
    >>> donne_les_directions(2, 1)
    ['S', 'E']
    """
    liste_directions = []
    for direction, tuple_delta in dictionnaire_vecteurs.items():
        delta_x, delta_y = tuple_delta
        x_temporaire = x + delta_x
        y_temporaire = y + delta_y
        if est_dans_la_liste(x_temporaire, y_temporaire):
            if est_déplaçable(cases[x][y],x_temporaire, y_temporaire):
                liste_directions.append(direction)
    return liste_directions

def donne_nb_cases_inaccessibles(x_départ: int, y_départ: int) -> int:
    """
    Cette fonction est le coeur du programme.
    Elle a pour paramètres des coordonnées (`x_départ`,`y_départ`) qui sont les coordonnées
    de départ du chemin dans `cases`
    Et elle renvoie le nombre de cases inaccessible
    >>> cases = [[4, 5, 3], [3, 2, 6], [4, 1, 1], [0, 1, 2]]
    >>> donne_nb_cases_inaccessibles(0,0)
    5
    """
    déja_vu = set()
    enfile((x_départ, y_départ))
    while len(file) != 0:
        x, y =file()
        déja_vu.add((x, y))
        liste_directions = donne_les_directions(x, y)
        for direction in liste_directions:
            delta_x, delta_y = dictionnaire_vecteurs[direction]
            x_temp = x + delta_x
            y_temp = y + delta_y
            if (x_temp, y_temp) in déja_vu:
                pass
            else:
                enfile((x_temp, y_temp))
    return (nb_lignes*nb_colonnes) - len(déja_vu)    



# tests
import doctest
doctest.testmod()


# Sortie
print(donne_nb_cases_inaccessibles(0, 0))

Proposition 3

def parcour_plateau(matrice, nb_ligne: int, nb_cologne: int):
    """ on parcour la matrice qui fait référence à 
    un plateau de jeu et on doit atteindre la dernier partie de la matrice 
    de plus on conte les casse qui sont inaccesible 
    >>> matrice = [['4', '5', '3'], ['3', '2', '6'], ['4', '1', '1'], ['0', '1', '2']]
    5
    >>> matrice = [['4', '5', '3'], ['5', '2', '6'], ['4', '1', '1'], ['0', '1', '2']]
    2
    """
    aller_droite = 0
    aller_bad = 0
    nb_inaccesible = 0
    while aller_droite < nb_cologne - 1 and aller_bad < nb_ligne - 1:
        if aller_droite < nb_cologne - 1:
            if matrice[aller_bad][aller_droite] < matrice[aller_bad][aller_droite + 1]:
                nb_inaccesible += 1
            else:
                if matrice[aller_bad][aller_droite] < matrice[aller_bad + 1][aller_droite]:
                    nb_inaccesible += 1
                    aller_droite += 1
                else:
                    aller_droite += 1
        if aller_bad < nb_ligne - 1: 
            if matrice[aller_bad][aller_droite] < matrice[aller_bad + 1][aller_droite]:
                nb_inaccesible += 1
            else:
                if matrice[aller_bad][aller_droite] < matrice[aller_bad][aller_droite + 1]:
                    nb_inaccesible += 1
                    aller_bad += 1
                else:
                    aller_bad += 1
    return nb_inaccesible

nb_ligne, nb_cologne = map(int, input().split())
matrice = [[[] for _ in range(nb_cologne)] for _ in range(nb_ligne)]
for x in range (nb_ligne):
    matrice[x] =  input().split()
print(parcour_plateau(matrice, nb_ligne, nb_cologne))
"""problème il est trop long"""

Proposition 4

# 0- Coeur du programme

def est_dans_le_tableau(nb_lignes: int, nb_colonnes: int, i: int, j: int) -> bool:
    """ Renvoie True si le curseur i,j est dans le tableau
    >>> est_dans_le_tableau(4, 3, 0, 0)
    True
    >>> est_dans_le_tableau(10, 10, 10, 10)
    False
    """
    if 0 <= i < nb_lignes and 0 <= j < nb_colonnes:
        return True
    else:
        return False

def déterminer_accessibilité(nb_lignes: int, nb_colonnes: int, tableau:list, est_accessible: list, i: int, j: int) -> list: 
    """ Fonction récursive renvoyant est_accessible, la liste des cases accessibles ou non du tableau.
    >>> tableau = [[4,5,3], [3,2,6], [4,1,1], [0,1,2]]
    >>> est_accessible = [[False, False, False], [False, False, False], [False, False, False], [False, False, False]]
    >>> déterminer_accessibilité(4, 3, tableau, est_accessible, 0, 0)
    [[True, False, False], [True, True, False], [False, True, True], [True, True, False]]
    """
    est_accessible[i][j] = True
    # Chaque combinaison correspond à une direction à tester, dans l'ordre on a : 
    # Droite, Gauche, Bas , Haut
    combinaisons = [(0,1), (0,-1), (1,0), (-1,0)]
    for x,y in combinaisons:                                            # Pour chaque combinaisons, 
        if est_dans_le_tableau(nb_lignes, nb_colonnes, i+x, j+y) :      # - On vérifie si le point se trouve dans le tableau
            if tableau[i+x][j+y] <= tableau[i][j]:                      # - Que le tableau de ce point soit plus petit que le précedent
                if est_accessible[i+x][j+y] == False :                  # - Que l'on est pas déjà passé par ce point (on évite de boucler)
                    est_accessible = déterminer_accessibilité(nb_lignes, nb_colonnes, tableau, est_accessible, i+x, j+y)
    return est_accessible
                

def nb_cases_inaccessibles(nb_lignes: int, nb_colonnes: int, tableau: list) -> int:
    """Renvoie le nombre de cases inaccessibles
    >>> nb_cases_inaccessibles(2, 2, [[1,2], [1,0]])
    1
    >>> nb_cases_inaccessibles(4, 3, [[4,5,3], [3,2,6], [4,1,1], [0,1,2]])
    5
    """
    est_accessible = [[False for _ in range(nb_colonnes)] for _ in range(nb_lignes)]
    déterminer_accessibilité(nb_lignes, nb_colonnes, tableau, est_accessible, 0, 0)
    compteur = 0
    for ligne in est_accessible:
        for x in ligne:
            if x is False:
                compteur += 1
    return compteur

# 1- Tests

import doctest
doctest.testmod()

# 2- Lecture des entrées

import sys
nb_lignes, nb_colonnes = map(int,input().split())
sys.setrecursionlimit(1000000)

# 3- Appel de la fonction / Sortie

tableau = [list(map(int,input().split())) for _ in range(nb_lignes)]
print(nb_cases_inaccessibles(nb_lignes, nb_colonnes, tableau))

Corrigé du professeur

Parcours récursif

"""
auteur : Franck CHAMBON
https://prologin.org/train/2003/qualification/cases_inaccessibles
"""

nb_lignes, nb_colonnes  = map(int, input().split())
grille = [list(map(int, input().split())) for i in range(nb_lignes)]

vu = [[False for _ in range(nb_colonnes)] for _ in range(nb_lignes)]

def est_valide(i: int, j: int) -> bool:
    """Renvoie la réponse : (i, j) est-elle une position valide sur la grille ?
    """
    return (0 <= i < nb_lignes) and (0 <= j < nb_colonnes)

def sont_connectés(i: int, j: int, idi: int, jdj: int) -> bool:
    """Renvoie la réponse : Peut-on se déplacer de (i, j) vers (idi, jdj) ?
    """
    return grille[idi][jdj] <= grille[i][j]

def visite(i: int, j: int) -> None:
    """Visite récursivement le graphe"""
    if vu[i][j] == False:
        vu[i][j] = True
        for (di, dj) in [(0, +1), (0, -1), (+1, 0), (-1, 0)]:
            idi, jdj = i + di, j + dj
            if est_valide(idi, jdj) and sont_connectés(i, j, idi, jdj):
                visite(idi, jdj)

visite(0, 0)
nb_inaccessibles = 0
for i in range(nb_lignes):
    for j in range(nb_colonnes):
        if vu[i][j] == False:
            nb_inaccessibles += 1
print(nb_inaccessibles)

Parcours itératif

"""
auteur : Franck CHAMBON
https://prologin.org/train/2003/qualification/cases_inaccessibles
"""

class Pile:
    def __init__(self):
        self.pile = []
    
    def est_vide(self):
        return self.pile == []
    
    def empile(self, x):
        self.pile.append(x)

    def dépile(self):
        if self.est_vide():
            raise ValueError("Pile vide")
        else:
            return self.pile.pop()

def nb_inaccessibles(grille: list[list[int]]) -> int:
    """Renvoie le nombre de cases inaccessibles.
    * On part en haut à gauche.
    * On bouge 1 case horizontalement ou verticalement,
        + si la valeur est inférieure ou égale.
    * Exemple : 5, 3,   6,   4   et   2  sont inaccessibles.

    >>> ligne_1 = [4, 5, 3]
    >>> ligne_2 = [3, 2, 6]
    >>> ligne_3 = [4, 1, 1]
    >>> ligne_4 = [0, 1, 2]
    >>> nb_inaccessibles([ligne_1, ligne_2, ligne_3, ligne_4])
    5

    """

    nb_lignes = len(grille)
    nb_colonnes = len(grille[0])

    def est_valide(i: int, j: int) -> bool:
        """Renvoie la réponse : (i, j) est-elle une position valide sur la grille ?
        """
        return (0 <= i < nb_lignes) and (0 <= j < nb_colonnes)

    def sont_connectés(i: int, j: int, idi: int, jdj: int) -> bool:
        """Renvoie la réponse : Peut-on se déplacer de (i, j) vers (idi, jdj) ?
        """
        return grille[idi][jdj] <= grille[i][j]
    

    est_marqué = [[False for j in range(nb_colonnes)] for i in range(nb_lignes)]
    # est_marqué est une matrice de booléen, indiquant si la case (i, j) a été visitée, ou programmée.
    nb_accessibles = 0

    sommets_à_traiter = Pile()
    sommets_à_traiter.empile((0, 0))
    est_marqué[0][0] = True
    nb_accessibles += 1

    while not sommets_à_traiter.est_vide():
        (i, j) = sommets_à_traiter.dépile()
        for (di, dj) in [(0, +1), (0, -1), (+1, 0), (-1, 0)]:
            idi, jdj = i + di, j + dj
            if est_valide(idi, jdj) and sont_connectés(i, j, idi, jdj):
                if not est_marqué[idi][jdj]:
                    sommets_à_traiter.empile((idi, jdj))
                    est_marqué[idi][jdj] = True
                    nb_accessibles += 1

    return nb_lignes * nb_colonnes - nb_accessibles

import doctest
doctest.testmod()

nb_lignes, nb_colonnes  = map(int, input().split())
grille = [list(map(int, input().split())) for i in range(nb_lignes)]

print(nb_inaccessibles(grille))