Ecrire des algorithmes A * (A-star) en Python

Aperçu

Quoi utiliser

Qu'est-ce que l'algorithme A \ *?

Cet algorithme utilise la somme de g comme distance au point actuel et la valeur estimée h à l'objectif comme paramètres pendant la recherche, et calcule la distance la plus courte de l'itinéraire avec un coût de recherche relativement faible. Il est souvent utilisé dans les jeux.

Il y a des cas où la distance la plus courte n'est pas calculée alors que le coût de recherche est faible (recherche en profondeur d'abord / DFS) et la recherche avec priorité à la largeur (recherche en largeur d'abord / BFS) où la distance la plus courte peut être calculée mais le coût de calcul a tendance à être élevé. ) A tendance à donner de meilleurs résultats.

Il était à l'origine basé sur un article publié en 1968. Le groupe d'algorithmes qui trouvent la distance la plus courte est décrit comme "acceptable - Admissible" dans l'article, donc le premier A est l'ensemble des algorithmes, et celui qui optimise le nombre d'évaluations pour le calcul d'itinéraire le plus court est A \ * La notation est à l'origine du nom de l'algorithme.

Lien de l'article: Une base formelle pour la détermination heuristique des chemins de coût minimum

Contenu du calcul

Le coût total du déplacement d'une position (nœud / nœud) vers n est $ f (n) $, le coût de la position de départ au nœud à la position n est $ g (n) $, et une valeur appelée heuristique. En supposant que la fonction qui calcule (valeur estimée de la distance au but) est $ h (n) $, $ f (n) $ est calculé par la somme suivante.

f(n) = g(n) + h(n)

Bien que non rigoureux, cet article calcule simplement $ g (n) $ comme le nombre de nœuds déplacés depuis le début (simplement 1 pour chaque déplacement) pour simplifier le calcul. Correspondant sous forme d'ajout).

De plus, le calcul de $ h (n) $ sera résolu cette fois. Étant donné que le labyrinthe est un type de grille qui peut être déplacé vers le haut, le bas, la gauche et la droite, la distance Manhattan est utilisée, où un mouvement vers le haut, le bas, la gauche et la droite est la distance 1.

Implémentation en Python

J'écrirai le code en Python pour comprendre.

Correspondance de code pour faire un labyrinthe de grille

Pour le processus de création d'une grille de labyrinthe, utilisez celle de "Recherche de priorité en profondeur d'écriture en Python". Les détails de l'explication sont écrits dans le lien, je vais donc l'omettre, mais il sera résumé comme suit.

-Représenté par une grille de lignes et de colonnes définies.

from __future__ import annotations
from typing import Optional
from typing import List
import random

#Valeur constante pour l'entrée.
CELL_TYPE_START: str = 'S'

#Valeur constante pour les passages.
CELL_TYPE_PASSAGE: str = ' '

#La valeur constante du mur.
CELL_TYPE_WALL: str = 'W'

#Quitter la valeur constante.
CELL_TYPE_GOAL: str = 'G'

#Une valeur constante pour le chemin d'itinéraire calculé.
CELL_TYPE_PATH: str = '*'


class Location:

    def __init__(self, row: int, column: int) -> None:
        """
Une classe qui gère une seule information de position de la grille du labyrinthe.

        Parameters
        ----------
        row : int
Le numéro de ligne de la position. Commencez par 0, 1 de haut en bas
Sera ajouté.
        column : int
Le numéro de colonne de la position. Partir de 0, 1 de gauche à droite
Sera ajouté.
        """
        self.row: int = row
        self.column: int = column


class Maze:

    #Le nombre vertical de grilles de labyrinthe à générer.
    _ROW_NUM: int = 7

    #Le numéro à côté de la grille du labyrinthe à générer.
    _COLUMN_NUM: int = 15

    #Pourcentage de murs à générer. 1.Plus il est proche de 0, plus il y a de murs.
    _WALL_SPARSENESS: float = 0.3

    def __init__(self) -> None:
        """
Une classe qui gère la génération et le contrôle d'une grille de labyrinthe aléatoire.

        Notes
        -----
Étant donné que chaque type de cellule est défini au hasard, ce n'est pas toujours depuis le début
Notez que vous ne pouvez rien faire qui puisse atteindre votre objectif.
        """

        self._set_start_and_goal_location()
        self._grid: List[List[str]] = []
        self._fill_grid_by_passage_cell()
        self._set_wall_type_to_cells_randomly()
        self._set_start_and_goal_type_to_cell()

    def _set_start_and_goal_location(self) -> None:
        """
Définissez les attributs des coordonnées du point de départ (entrée) et de l'objectif (sortie).
        """
        self.start_loc: Location = Location(row=0, column=0)
        self.goal_loc: Location = Location(
            row=self._ROW_NUM - 1,
            column=self._COLUMN_NUM - 1)

    def _fill_grid_by_passage_cell(self) -> None:
        """
Ajoutez des cellules à toutes les cellules et définissez le type de cellule du passage.
        """
        for row in range(self._ROW_NUM):
            row_cells: List[str] = []
            for column in range(self._COLUMN_NUM):
                row_cells.append(CELL_TYPE_PASSAGE)
            self._grid.append(row_cells)

    def _set_wall_type_to_cells_randomly(self) -> None:
        """
Définissez de manière aléatoire le type de cellule de paroi pour chaque cellule de la grille.
        """
        for row in range(self._ROW_NUM):
            for column in range(self._COLUMN_NUM):
                probability = random.uniform(0.0, 1.0)
                if probability >= self._WALL_SPARSENESS:
                    continue
                self._grid[row][column] = CELL_TYPE_WALL

    def _set_start_and_goal_type_to_cell(self) -> None:
        """
Aux positions de départ (entrée) et de but (sortie), respectivement
Définissez le type de cellule.
        """
        self._grid[self.start_loc.row][self.start_loc.column] = \
            CELL_TYPE_START
        self._grid[self.goal_loc.row][self.goal_loc.column] = \
            CELL_TYPE_GOAL

    def is_goal_loc(self, location: Location) -> bool:
        """
Obtient la valeur booléenne indiquant si la position spécifiée est la position cible.

        Parameters
        ----------
        location : Location
Position pour le jugement.

        Returns
        -------
        result : bool
True est défini s'il s'agit de la position cible.
        """
        if (location.row == self.goal_loc.row
                and location.column == self.goal_loc.column):
            return True
        return False

    def get_movable_locations(self, location: Location) -> List[Location]:
        """
Obtenez une liste des positions mobiles à partir de la position spécifiée.

        Parameters
        ----------
        location : Location
Une instance de la position de référence.

        Returns
        -------
        movable_locations : list of Location
Une liste contenant des instances d'emplacements mobiles.
        """
        movable_locations: List[Location] = []

        #Traitement du jugement pour savoir s'il peut ou non être déplacé vers le haut.
        if location.row + 1 < self._ROW_NUM:
            is_wall: bool = self._grid[location.row + 1][location.column] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row + 1, column=location.column))

        #Traitement du jugement pour savoir s'il peut être déplacé vers le bas.
        if location.row - 1 >= 0:
            is_wall = self._grid[location.row - 1][location.column] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row - 1, column=location.column))

        #Traitement du jugement pour savoir s'il peut être déplacé vers la droite.
        if location.column + 1 < self._COLUMN_NUM:
            is_wall = self._grid[location.row][location.column + 1] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row, column=location.column + 1))

        #Traitement du jugement pour savoir s'il peut être déplacé vers la gauche.
        if location.column - 1 >= 0:
            is_wall = self._grid[location.row][location.column - 1] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row, column=location.column - 1))

        return movable_locations

    def set_path_type_to_cells(self, path: List[Location]) -> None:
        """
Pour les cellules contenues dans le chemin spécifié vers l'entrée et la sortie
Définissez le type de cellule du chemin.

        Parameters
        ----------
        path : list of Location
Les informations de position de chaque cellule de l'entrée à la sortie obtenues par la recherche
Liste stockée.
        """
        for location in path:
            self._grid[location.row][location.column] = CELL_TYPE_PATH

        #Les pièces d'entrée et de sortie incluses dans le chemin sont d'origine
        #Reflétez le type de cellule.
        self._grid[self.start_loc.row][self.start_loc.column] = \
            CELL_TYPE_START
        self._grid[self.goal_loc.row][self.goal_loc.column] = \
            CELL_TYPE_GOAL

    def __str__(self) -> str:
        """
Obtient la chaîne de type pour chaque cellule de la grille.

        Returns
        -------
        grid_str : str
Une chaîne de types pour chaque cellule de la grille.
        """
        grid_str: str = ''
        for row_cells in self._grid:
            grid_str += '-' * self._COLUMN_NUM * 2
            grid_str += '\n'
            for cell_type in row_cells:
                grid_str += cell_type
                grid_str += '|'
            grid_str += '\n'
        return grid_str


class Node:

    def __init__(self, location: Location, parent: Optional[Node]):
        """
Données pour un seul nœud pour contenir des informations telles que la position et la transition du labyrinthe
Classe à gérer.

        Parameters
        ----------
        location : Location
Une instance qui gère les informations de localisation de la cible.
        parent : Node or None
Une instance d'un nœud qui gère les informations de position avant de se déplacer. Au début de la recherche
Etc. sera Aucun.
        """
        self.location: Location = location
        self.parent: Optional[Node] = parent


def get_path_from_goal_node(goal_node: Node) -> List[Location]:
    """
Depuis le nœud de sortie, le chemin depuis l'entrée obtenu en recherchant jusqu'à la sortie
avoir.

    Parameters
    ----------
    goal_node : Node
Une instance du nœud de sortie cible (objectif).

    Returns
    -------
    path : list of Location
Une liste contenant des instances de chaque emplacement de l'entrée à la sortie.
    """
    path: List[Location] = [goal_node.location]
    node: Node = goal_node
    while node.parent is not None:
        node = node.parent
        path.append(node.location)
    path.reverse()
    return path

Si vous essayez de sortir le labyrinthe généré comme un essai, il sera affiché dans une grille comme indiqué ci-dessous.

if __name__ == '__main__':
    maze = Maze()
    print(maze)
------------------------------
S| |W| |W| | | |W| |W| |W| |W|
------------------------------
W| | | | | |W| | | |W|W| |W|W|
------------------------------
W| | | | |W| | | |W|W| | | | |
------------------------------
 | |W| |W|W| | | | | | | |W|W|
------------------------------
W|W|W| | | |W| | | | | | | |W|
------------------------------
W|W| | | | | | | |W| |W| | | |
------------------------------
W|W| | | | |W| | | | | | | |G|

Création de files d'attente prioritaires et ajustement des classes de nœuds

L'algorithme A \ * utilise une file d'attente prioritaire comme structure de données. Il s'agit d'une file d'attente spéciale dans laquelle les valeurs sont ajoutées à la file d'attente avec priorité et lors de la récupération de la valeur, la valeur avec la priorité la plus élevée est également ciblée.

Vous devez ajouter la méthode __lt__ à la classe Node car la valeur de priorité est calculée par l'opérateur de comparaison inférieur à (<) pour l'instance du nœud que vous souhaitez ajouter à la file d'attente.

Dans la méthode __lt__, $ g (n) + h (n) $ est calculé dans la formule de coût et comparé à un autre nœud.

Ajustez également la valeur de $ g (n) $ et la valeur de $ h (n) $ afin qu'elles soient incluses dans les attributs de classe car elles sont requises pour le calcul.

class Node:

    def __init__(
            self, location: Location, parent: Optional[Node],
            cost: float, heuristic:float) -> None:
        """
Données pour un seul nœud pour contenir des informations telles que la position et la transition du labyrinthe
Classe à gérer.

        Parameters
        ----------
        location : Location
Une instance qui gère les informations de localisation de la cible.
        parent : Node or None
Une instance d'un nœud qui gère les informations de position avant de se déplacer. Au début de la recherche
Etc. sera Aucun.
        cost : float
Valeur de coût de la position de départ à la position du nœud correspondant (g(n)alors
Valeur obtenue).
        heuristic : float
Distance estimée de ce nœud à la sortie (h(n)Valeur obtenue par).
        """
        self.location: Location = location
        self.parent: Optional[Node] = parent
        self.cost = cost
        self.heuristic = heuristic

    def __lt__(self, other_node: Node) -> bool:
        """
Opérateur de comparaison( < )Méthode de traitement par.
Utilisé pour contrôler les files d'attente prioritaires.

        Parameters
        ----------
        other_node : Node
Instances d'autres nœuds à comparer.

        Returns
        -------
        result_bool : bool
Résultat de la comparaison. Le traitement du calcul est le coût de l'entrée (g(n))Quand
Valeur hulistique (h(n)) En comparant la valeur totale
Sera fait.
        """
        left_value: float = self.cost + self.heuristic
        right_value: float = other_node.cost + other_node.heuristic
        result_bool: bool = left_value < right_value
        return result_bool

Je n'ai jamais utilisé la méthode __lt__ seule, je vais donc essayer de créer plusieurs instances et de les comparer pour voir les résultats. La comparaison est basée sur la somme du coût et de l'heuristique, donc la première comparaison est False et la seconde est True.

if __name__ == '__main__':
    node_1 = Node(
        location=Location(row=0, column=0),
        parent=None,
        cost=2, heuristic=2)
    node_2 = Node(
        location=Location(row=0, column=0),
        parent=None,
        cost=1, heuristic=2)
    node_3 = Node(
        location=Location(row=0, column=0),
        parent=None,
        cost=3, heuristic=2)
    print(node_1 < node_2)
    print(node_1 < node_3)
False
True

Ensuite, nous préparerons la file d'attente prioritaire. Le traitement de la file d'attente prioritaire peut être géré à l'aide du package heapq du module standard Python.

import heapq
...


class PriorityQueue:

    def __init__(self) -> None:
        """
Une classe pour contrôler les files d'attente prioritaires.
        """
        self._container: List[Node] = []

    @property
    def empty(self) -> bool:
        """
Valeur d'attribut indiquant si la file d'attente est vide.

        Returns
        -------
        result : bool
Défini sur True si vide.
        """
        return not self._container

    def push(self, node: Node) -> None:
        """
Ajoutez une instance du nœud à la file d'attente.

        Parameters
        ----------
        node : Node
Instance du nœud à ajouter.
        """
        heapq.heappush(self._container, node)

    def pop(self) -> Node:
        """
Extrayez l'instance du nœud de priorité la plus élevée de la file d'attente.

        Returns
        -------
        node : Node
Une instance de la classe Node récupérée.
        """
        return heapq.heappop(self._container)

Déplaçons-le un peu et essayons-le pour voir s'il est traité d'une manière qui prend en compte la priorité. Il convient de noter que la forme «faible coût est préférable (= priorité élevée)». Il n'est pas retiré par pop de celui avec la valeur de coût totale la plus élevée.

if __name__ == '__main__':
    priority_queue = PriorityQueue()
    priority_queue.push(
        node=Node(
            location=Location(row=0, column=0),
            parent=None,
            cost=1,
            heuristic=1))
    priority_queue.push(
        node=Node(
            location=Location(row=0, column=0),
            parent=None,
            cost=3,
            heuristic=3))
    priority_queue.push(
        node=Node(
            location=Location(row=0, column=0),
            parent=None,
            cost=2,
            heuristic=3))

    node: Node = priority_queue.pop()
    print(node.cost, node.heuristic)
    node = priority_queue.pop()
    print(node.cost, node.heuristic)
    node = priority_queue.pop()
    print(node.cost, node.heuristic)

Vous pouvez voir que les résultats de sortie sont récupérés dans l'ordre croissant du coût total.

1 1
2 3
3 3

Ajout d'un traitement pour acquérir la distance de Manhattan entre la position cible et la position de sortie (but)

Puisque le calcul de l'heuristique ($ h (n) $) est cette fois un labyrinthe au format grille, la distance Manhattan est utilisée, alors ajoutez cette fonction à la classe Maze.

    def get_manhattan_distance(self, location: Location) -> int:
        """
La distance de Manhattan entre la position cible et la position de sortie (objectif)
avoir.

        Parameters
        ----------
        location : Location
Une instance de l'emplacement cible.

        Returns
        -------
        distance : int
Distance de Manhattan entre la position cible et la position de sortie. Direction de la colonne
La somme de la valeur absolue de la différence et de la valeur absolue de la différence dans le sens des lignes est définie.
        """
        x_distance: int = abs(location.column - self.goal_loc.column)
        y_distance: int = abs(location.row - self.goal_loc.column)
        distance: int = x_distance + y_distance
        return distance

A \ * Ajout de la fonction d'algorithme

Maintenant que nous avons toutes les préparations nécessaires, créons une fonction pour l'algorithme A \ *. Le nom de la fonction est astar.

from typing import Callable, Dict
...

def astar(
        init_loc: Location,
        is_goal_loc_method: Callable[[Location], bool],
        get_movable_locations_method: Callable[[Location], List[Location]],
        hueristic_method: Callable[[Location], int],
        ) -> Optional[Node]:
    """
    A*Effectuer le traitement de la recherche par algorithme.

    Parameters
    ----------
    init_loc : Location
Recherche de la position de départ (position de l'entrée du labyrinthe).
    is_goal_loc_method : callable
Une méthode qui détermine si la position cible est la sortie (objectif).
    get_movable_locations_method : callable
Une méthode pour obtenir une liste de positions de cellule à déplacer de la position cible.
    hueristic_method : callable
Obtenez la distance entre la position cible et la position de sortie (objectif)
Une méthode d'heuristique pour.

    Returns
    -------
    goal_node : Node or None
Une instance du nœud à l'emplacement de sortie calculé. Vers la sortie
Aucun n'est défini dans les cas où l'itinéraire ne peut pas être calculé.
    """
    frontier_queue: PriorityQueue = PriorityQueue()
    frontier_queue.push(
        node=Node(
            location=init_loc,
            parent=None,
            cost=0,
            heuristic=hueristic_method(init_loc)))

    explored_loc_cost_dict: Dict[Location, float] = {init_loc: 0.0}

    while not frontier_queue.empty:
        current_node: Node = frontier_queue.pop()
        current_loc: Location = current_node.location

        if is_goal_loc_method(current_loc):
            return current_node

        movable_locations = get_movable_locations_method(current_loc)
        for movable_location in movable_locations:
            new_cost: float = current_node.cost + 1

            #Une nouvelle destination a déjà été recherchée et ce n'est pas rentable
            #Si tel est le cas, ignorez-le.
            if (movable_location in explored_loc_cost_dict and
                    explored_loc_cost_dict[movable_location] <= new_cost):
                continue

            explored_loc_cost_dict[movable_location] = new_cost
            frontier_queue.push(
                node=Node(
                    location=movable_location,
                    parent=current_node,
                    cost=new_cost,
                    heuristic=hueristic_method(movable_location)))
    return None

J'ai écrit plus tôt, "Ecrire une recherche de priorité en profondeur en Python. », le flux est similaire à la fonction dfs dans l'article. Le traitement étant partiellement différent, je vais principalement expliquer la différence.

Tout d'abord, comme pour l'argument, le Callable (hueristic_method) pour l'heuristique est ajouté. Cela spécifie la méthode de calcul de la distance de Manhattan qui a été ajoutée il y a quelque temps dans cet exemple.

    frontier_queue: PriorityQueue = PriorityQueue()
    frontier_queue.push(
        node=Node(
            location=init_loc,
            parent=None,
            cost=0,
            heuristic=hueristic_method(init_loc)))

De plus, la file d'attente a été remplacée par celle de la file d'attente prioritaire (frontier_queue). Nous ajoutons des nœuds à explorer à cette file d'attente.

    explored_loc_cost_dict: Dict[Location, float] = {init_loc: 0.0}

Le dictionnaire qui contient les nœuds recherchés se présente sous la forme de maintien de la position dans la clé et de la valeur du coût ($ g (n) $) dans la valeur. Comme il s'agit d'une méthode simple qui s'incrémente de 1 à chaque fois que vous vous déplacez, définissez d'abord 0,0.

    while not frontier_queue.empty:
        current_node: Node = frontier_queue.pop()
        current_loc: Location = current_node.location

Le processus est répété dans une boucle while jusqu'à ce que le nœud à rechercher disparaisse de la file d'attente.

        if is_goal_loc_method(current_loc):
            return current_node

Si le nœud cible est une sortie (objectif), le processus est terminé en renvoyant le nœud de sortie.

        movable_locations = get_movable_locations_method(current_loc)
        for movable_location in movable_locations:
            new_cost: float = current_node.cost + 1

Chaque position à déplacer du nœud cible de recherche actuel est traitée par une boucle for. Cette fois, il est implémenté sous une forme simple qui incrémente le coût ($ g (n) $) de 1 à chaque déplacement.

            #Une nouvelle destination a déjà été recherchée et ce n'est pas rentable
            #Si tel est le cas, ignorez-le.
            if (movable_location in explored_loc_cost_dict and
                    explored_loc_cost_dict[movable_location] <= new_cost):
                continue

Même si l'emplacement a déjà été recherché, il vaut la peine de chercher s'il est rentable. S'il a déjà été recherché et que la position ne change pas ou se détériore en termes de coût, il n'y a aucun intérêt à rechercher, alors sautez-la.

            explored_loc_cost_dict[movable_location] = new_cost
            frontier_queue.push(
                node=Node(
                    location=movable_location,
                    parent=current_node,
                    cost=new_cost,
                    heuristic=hueristic_method(movable_location)))

Si les conditions valent la peine d'être recherchées, nous définissons des valeurs telles que le coût dans le dictionnaire recherché et ajoutons des nœuds à la file d'attente prioritaire à rechercher.

Étant donné que les éléments avec la priorité la plus élevée sont traités en premier, vous pouvez effectuer une recherche avec un excellent coût de recherche.

    return None

En fonction du labyrinthe généré, il se peut qu'il n'y ait pas de route vers la sortie, donc dans ce cas Aucun n'est retourné.

Je vais essayer.

if __name__ == '__main__':
    maze: Maze = Maze()
    print(maze)

    goal_node: Optional[Node] = astar(
        init_loc=maze.start_loc,
        is_goal_loc_method=maze.is_goal_loc,
        get_movable_locations_method=maze.get_movable_locations,
        hueristic_method=maze.get_manhattan_distance,
    )
    if goal_node is None:
        print('C'est un labyrinthe où la sortie ne peut être calculée.')
    else:
        print('-' * 20)
        path: List[Location] = get_path_from_goal_node(
            goal_node=goal_node)
        maze.set_path_type_to_cells(path=path)
        print('Chemin calculé:')
        print(maze)
Chemin calculé:
------------------------------
S|*|*|*|*| | | |W| | | |W|W|W|
------------------------------
W| | |W|*|*|W|W|W| | | | |W| |
------------------------------
W|W| |W|W|*|W| | | | |W|W|W|W|
------------------------------
W|W|W| | |*|*|W| |W| | | | | |
------------------------------
 |W|W|W| | |*|W| | | | | |W| |
------------------------------
 | | | | |W|*|*|*|*|*| | |W| |
------------------------------
 |W| | |W|W|W| |W| |*|*|*|*|G|

S sur la grille est l'entrée (début), G est la sortie (but) et la partie astérisque est le chemin calculé par la recherche. Le chemin de l'entrée à la sortie a été calculé en toute sécurité.

C'est une caractéristique de A \ * qu'un chemin diagonal est obtenu par rapport à la recherche de priorité de largeur où le résultat de la recherche est légèrement à angle droit. Même l'échantillon que j'ai essayé cette fois-ci a un chemin légèrement diagonal.

Code entier

from __future__ import annotations
from typing import Optional
from typing import List
import random
import heapq
from typing import Callable, Dict

#Valeur constante pour l'entrée.
CELL_TYPE_START: str = 'S'

#Valeur constante pour les passages.
CELL_TYPE_PASSAGE: str = ' '

#La valeur constante du mur.
CELL_TYPE_WALL: str = 'W'

#Quitter la valeur constante.
CELL_TYPE_GOAL: str = 'G'

#Une valeur constante pour le chemin d'itinéraire calculé.
CELL_TYPE_PATH: str = '*'


class Location:

    def __init__(self, row: int, column: int) -> None:
        """
Une classe qui gère une seule information de position de la grille du labyrinthe.

        Parameters
        ----------
        row : int
Le numéro de ligne de la position. Commencez par 0, 1 de haut en bas
Sera ajouté.
        column : int
Le numéro de colonne de la position. Partir de 0, 1 de gauche à droite
Sera ajouté.
        """
        self.row: int = row
        self.column: int = column


class Maze:

    #Le nombre vertical de grilles de labyrinthe à générer.
    _ROW_NUM: int = 7

    #Le numéro à côté de la grille du labyrinthe à générer.
    _COLUMN_NUM: int = 15

    #Pourcentage de murs à générer. 1.Plus il est proche de 0, plus il y a de murs.
    _WALL_SPARSENESS: float = 0.3

    def __init__(self) -> None:
        """
Une classe qui gère la génération et le contrôle d'une grille de labyrinthe aléatoire.

        Notes
        -----
Étant donné que chaque type de cellule est défini au hasard, ce n'est pas toujours depuis le début
Notez que vous ne pouvez rien faire qui puisse atteindre votre objectif.
        """

        self._set_start_and_goal_location()
        self._grid: List[List[str]] = []
        self._fill_grid_by_passage_cell()
        self._set_wall_type_to_cells_randomly()
        self._set_start_and_goal_type_to_cell()

    def _set_start_and_goal_location(self) -> None:
        """
Définissez les attributs des coordonnées du point de départ (entrée) et de l'objectif (sortie).
        """
        self.start_loc: Location = Location(row=0, column=0)
        self.goal_loc: Location = Location(
            row=self._ROW_NUM - 1,
            column=self._COLUMN_NUM - 1)

    def _fill_grid_by_passage_cell(self) -> None:
        """
Ajoutez des cellules à toutes les cellules et définissez le type de cellule du passage.
        """
        for row in range(self._ROW_NUM):
            row_cells: List[str] = []
            for column in range(self._COLUMN_NUM):
                row_cells.append(CELL_TYPE_PASSAGE)
            self._grid.append(row_cells)

    def _set_wall_type_to_cells_randomly(self) -> None:
        """
Définissez de manière aléatoire le type de cellule de paroi pour chaque cellule de la grille.
        """
        for row in range(self._ROW_NUM):
            for column in range(self._COLUMN_NUM):
                probability = random.uniform(0.0, 1.0)
                if probability >= self._WALL_SPARSENESS:
                    continue
                self._grid[row][column] = CELL_TYPE_WALL

    def _set_start_and_goal_type_to_cell(self) -> None:
        """
Aux positions de départ (entrée) et de but (sortie), respectivement
Définissez le type de cellule.
        """
        self._grid[self.start_loc.row][self.start_loc.column] = \
            CELL_TYPE_START
        self._grid[self.goal_loc.row][self.goal_loc.column] = \
            CELL_TYPE_GOAL

    def is_goal_loc(self, location: Location) -> bool:
        """
Obtient la valeur booléenne indiquant si la position spécifiée est la position cible.

        Parameters
        ----------
        location : Location
Position pour le jugement.

        Returns
        -------
        result : bool
True est défini s'il s'agit de la position cible.
        """
        if (location.row == self.goal_loc.row
                and location.column == self.goal_loc.column):
            return True
        return False

    def get_movable_locations(self, location: Location) -> List[Location]:
        """
Obtenez une liste des positions mobiles à partir de la position spécifiée.

        Parameters
        ----------
        location : Location
Une instance de la position de référence.

        Returns
        -------
        movable_locations : list of Location
Une liste contenant des instances d'emplacements mobiles.
        """
        movable_locations: List[Location] = []

        #Traitement du jugement pour savoir s'il peut ou non être déplacé vers le haut.
        if location.row + 1 < self._ROW_NUM:
            is_wall: bool = self._grid[location.row + 1][location.column] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row + 1, column=location.column))

        #Traitement du jugement pour savoir s'il peut être déplacé vers le bas.
        if location.row - 1 >= 0:
            is_wall = self._grid[location.row - 1][location.column] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row - 1, column=location.column))

        #Traitement du jugement pour savoir s'il peut être déplacé vers la droite.
        if location.column + 1 < self._COLUMN_NUM:
            is_wall = self._grid[location.row][location.column + 1] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row, column=location.column + 1))

        #Traitement du jugement pour savoir s'il peut être déplacé vers la gauche.
        if location.column - 1 >= 0:
            is_wall = self._grid[location.row][location.column - 1] \
                == CELL_TYPE_WALL
            if not is_wall:
                movable_locations.append(
                    Location(row=location.row, column=location.column - 1))

        return movable_locations

    def set_path_type_to_cells(self, path: List[Location]) -> None:
        """
Pour les cellules contenues dans le chemin spécifié vers l'entrée et la sortie
Définissez le type de cellule du chemin.

        Parameters
        ----------
        path : list of Location
Les informations de position de chaque cellule de l'entrée à la sortie obtenues par la recherche
Liste stockée.
        """
        for location in path:
            self._grid[location.row][location.column] = CELL_TYPE_PATH

        #Les pièces d'entrée et de sortie incluses dans le chemin sont d'origine
        #Reflétez le type de cellule.
        self._grid[self.start_loc.row][self.start_loc.column] = \
            CELL_TYPE_START
        self._grid[self.goal_loc.row][self.goal_loc.column] = \
            CELL_TYPE_GOAL

    def get_manhattan_distance(self, location: Location) -> int:
        """
La distance de Manhattan entre la position cible et la position de sortie (objectif)
avoir.

        Parameters
        ----------
        location : Location
Une instance de l'emplacement cible.

        Returns
        -------
        distance : int
Distance de Manhattan entre la position cible et la position de sortie. Direction de la colonne
La somme de la valeur absolue de la différence et de la valeur absolue de la différence dans le sens des lignes est définie.
        """
        x_distance: int = abs(location.column - self.goal_loc.column)
        y_distance: int = abs(location.row - self.goal_loc.column)
        distance: int = x_distance + y_distance
        return distance

    def __str__(self) -> str:
        """
Obtient la chaîne de type pour chaque cellule de la grille.

        Returns
        -------
        grid_str : str
Une chaîne de types pour chaque cellule de la grille.
        """
        grid_str: str = ''
        for row_cells in self._grid:
            grid_str += '-' * self._COLUMN_NUM * 2
            grid_str += '\n'
            for cell_type in row_cells:
                grid_str += cell_type
                grid_str += '|'
            grid_str += '\n'
        return grid_str


class Node:

    def __init__(
            self, location: Location, parent: Optional[Node],
            cost: float, heuristic:float) -> None:
        """
Données pour un seul nœud pour contenir des informations telles que la position et la transition du labyrinthe
Classe à gérer.

        Parameters
        ----------
        location : Location
Une instance qui gère les informations de localisation de la cible.
        parent : Node or None
Une instance d'un nœud qui gère les informations de position avant de se déplacer. Au début de la recherche
Etc. sera Aucun.
        cost : float
Valeur de coût de la position de départ à la position du nœud correspondant (g(n)alors
Valeur obtenue).
        heuristic : float
Distance estimée de ce nœud à la sortie (h(n)Valeur obtenue par).
        """
        self.location: Location = location
        self.parent: Optional[Node] = parent
        self.cost = cost
        self.heuristic = heuristic

    def __lt__(self, other_node: Node) -> bool:
        """
Opérateur de comparaison( < )Méthode de traitement par.
Utilisé pour contrôler les files d'attente prioritaires.

        Parameters
        ----------
        other_node : Node
Instances d'autres nœuds à comparer.

        Returns
        -------
        result_bool : bool
Résultat de la comparaison. Le traitement du calcul est le coût de l'entrée (g(n))Quand
Valeur hulistique (h(n)) En comparant la valeur totale
Sera fait.
        """
        left_value: float = self.cost + self.heuristic
        right_value: float = other_node.cost + other_node.heuristic
        result_bool: bool = left_value < right_value
        return result_bool


def get_path_from_goal_node(goal_node: Node) -> List[Location]:
    """
Depuis le nœud de sortie, le chemin depuis l'entrée obtenu en recherchant jusqu'à la sortie
avoir.

    Parameters
    ----------
    goal_node : Node
Une instance du nœud de sortie cible (objectif).

    Returns
    -------
    path : list of Location
Une liste contenant des instances de chaque emplacement de l'entrée à la sortie.
    """
    path: List[Location] = [goal_node.location]
    node: Node = goal_node
    while node.parent is not None:
        node = node.parent
        path.append(node.location)
    path.reverse()
    return path


class PriorityQueue:

    def __init__(self) -> None:
        """
Une classe pour contrôler les files d'attente prioritaires.
        """
        self._container: List[Node] = []

    @property
    def empty(self) -> bool:
        """
Valeur d'attribut indiquant si la file d'attente est vide.

        Returns
        -------
        result : bool
Défini sur True si vide.
        """
        return not self._container

    def push(self, node: Node) -> None:
        """
Ajoutez une instance du nœud à la file d'attente.

        Parameters
        ----------
        node : Node
Instance du nœud à ajouter.
        """
        heapq.heappush(self._container, node)

    def pop(self) -> Node:
        """
Extrayez l'instance du nœud de priorité la plus élevée de la file d'attente.

        Returns
        -------
        node : Node
Une instance de la classe Node récupérée.
        """
        return heapq.heappop(self._container)


def astar(
        init_loc: Location,
        is_goal_loc_method: Callable[[Location], bool],
        get_movable_locations_method: Callable[[Location], List[Location]],
        hueristic_method: Callable[[Location], int],
        ) -> Optional[Node]:
    """
    A*Effectuer le traitement de la recherche par algorithme.

    Parameters
    ----------
    init_loc : Location
Recherche de la position de départ (position de l'entrée du labyrinthe).
    is_goal_loc_method : callable
Une méthode qui détermine si la position cible est la sortie (objectif).
    get_movable_locations_method : callable
Une méthode pour obtenir une liste de positions de cellule à déplacer de la position cible.
    hueristic_method : callable
Obtenez la distance entre la position cible et la position de sortie (objectif)
Une méthode d'heuristique pour.

    Returns
    -------
    goal_node : Node or None
Une instance du nœud à l'emplacement de sortie calculé. Vers la sortie
Aucun n'est défini dans les cas où l'itinéraire ne peut pas être calculé.
    """
    frontier_queue: PriorityQueue = PriorityQueue()
    frontier_queue.push(
        node=Node(
            location=init_loc,
            parent=None,
            cost=0,
            heuristic=hueristic_method(init_loc)))

    explored_loc_cost_dict: Dict[Location, float] = {init_loc: 0.0}

    while not frontier_queue.empty:
        current_node: Node = frontier_queue.pop()
        current_loc: Location = current_node.location

        if is_goal_loc_method(current_loc):
            return current_node

        movable_locations = get_movable_locations_method(current_loc)
        for movable_location in movable_locations:
            new_cost: float = current_node.cost + 1

            #Une nouvelle destination a déjà été recherchée et ce n'est pas rentable
            #Si tel est le cas, ignorez-le.
            if (movable_location in explored_loc_cost_dict and
                    explored_loc_cost_dict[movable_location] <= new_cost):
                continue

            explored_loc_cost_dict[movable_location] = new_cost
            frontier_queue.push(
                node=Node(
                    location=movable_location,
                    parent=current_node,
                    cost=new_cost,
                    heuristic=hueristic_method(movable_location)))
    return None


if __name__ == '__main__':
    maze: Maze = Maze()
    print(maze)

    goal_node: Optional[Node] = astar(
        init_loc=maze.start_loc,
        is_goal_loc_method=maze.is_goal_loc,
        get_movable_locations_method=maze.get_movable_locations,
        hueristic_method=maze.get_manhattan_distance,
    )
    if goal_node is None:
        print('C'est un labyrinthe où la sortie ne peut être calculée.')
    else:
        print('-' * 20)
        path: List[Location] = get_path_from_goal_node(
            goal_node=goal_node)
        maze.set_path_type_to_cells(path=path)
        print('Chemin calculé:')
        print(maze)

De côté

――Depuis que j'étais à l'origine diplômé d'une école dans le domaine du design, veuillez pardonner à Masakari qui a de solides connaissances dans le domaine de l'informatique.

Références / Résumé du site

Recommended Posts

Ecrire des algorithmes A * (A-star) en Python
Ecrire une méthode de cupidité simple en Python
Ecrire une dichotomie en Python
Ecrire un graphique à secteurs en Python
Ecrire le plugin vim en Python
Écrire une recherche de priorité en profondeur en Python
Implémentation d'un algorithme simple en Python 2
Exécutez un algorithme simple en Python
Ecrire le test dans la docstring python
Ecrire une courte définition de propriété en Python
Ecrire un programme de chiffrement Caesar en Python
Ecrire un plugin Vim simple en Python 3
Algorithme A * (édition Python)
Algorithme génétique en python
Ecrire Python dans MySQL
Algorithme en Python (méthode Bellman-Ford, Bellman-Ford)
Algorithme en Python (Dijkstra)
Prendre une capture d'écran en Python
Ecrire des filtres Pandec en Python
Créer un dictionnaire en Python
Écrire une distribution bêta en Python
Algorithme en Python (jugement premier)
Reproduire la méthode de division mutuelle euclidienne en Python
Algorithme en Python (dichotomie)
Créer un bookmarklet en Python
Implémenter l'algorithme de Dijkstra en python
Dessinez un cœur en Python
Ecrire un programme de dynamique moléculaire super simple en python
Je veux écrire en Python! (2) Écrivons un test
Ecrire un histogramme à l'échelle logarithmique sur l'axe des x en python
Algorithme en Python (recherche de priorité de largeur, bfs)
Ecrire un test piloté par table en C
Appuyez sur une commande en Python (Windows)
Ecrire un schéma JSON avec Python DSL
Créer un conteneur DI avec Python
Il est difficile d'écrire un algorithme très simple en php
Ecrire un serveur HTTP / 2 en Python
Dessinez une matrice de diagramme de dispersion avec python
Ecrire une fonction AWS Lambda en Python
ABC166 en Python A ~ C problème
Algorithme Python
Développons un algorithme d'investissement avec Python 2
Créer un fichier binaire en Python
Algorithme en Python (recherche de priorité en profondeur, dfs)
Ecrire le code de test du sélénium en python
Résoudre ABC036 A ~ C avec Python
Algorithme (arborescence de segments) en Python (s'entraîner)
Résoudre ABC037 A ~ C avec Python
Dessinez un diagramme CNN en Python
Créer une chaîne aléatoire en Python
Ecrire un test unitaire de langage C en Python
Ecrire un script batch avec Python3.5 ~
Lors de l'écriture d'un programme en Python
Écrire de la documentation dans Sphinx avec Python Livereload
Générer une collection de première classe en Python
Livre en spirale en Python! Python avec un livre en spirale! (Chapitre 14 ~)
Résoudre ABC175 A, B, C avec Python
Un client HTTP simple implémenté en Python
Faites une visite Euler non récursive en Python