Recherche

Solutions du code

Module .../moteurs_recherche/element.py

class Element:
    """ Un élément dans l'espace de recherche. """

    def __init__(self, nom=''):
        """
            :param str nom: nom de l'élément (optionnel).
        """
        self.nom = nom

    def distance(self, element):
        """ Retourne la distance entre l'éléments courant et un autre.

            (A implémenter différemment dans les sous-classes, si nécessaire.)
        """

        return 1

    def __eq__(self, autre):
        return self.nom == autre.nom

    def __hash__(self):
        return hash(str(self))

    def __repr__(self):
        return '{}'.format(self.nom)

Module .../moteurs_recherche/ville.py

from math import sqrt
from .element import Element

class Ville(Element):
    """ Un élément représentant une ville.
    """

    def __init__(self, x, y, nom=''):
        """
            :param x: coordonnée en x de la ville.
            :param y: coordonnée en y de la ville.
            :param str nom: nom de la ville.
        """

        Element.__init__(self, nom)
        self.x = x
        self.y = y

    def distance(self, ville):
        """ Distance euclidienne entre la ville courante et la ville ``ville``. """
        return sqrt((self.x-ville.x)**2 + (self.y-ville.y)**2)

    def __eq__(self, autre):
        if not isinstance(autre, Ville):
            return False
        return self.x == autre.x and self.y == autre.y and self.nom == autre.nom

    def __hash__(self):
        return hash(str(self))

    def __repr__(self):
        return '{}({}, {})'.format(self.nom, self.x, self.y)

Module .../moteurs_recherche/espace.py

from copy import copy

class Espace:
    """ Un espace de recherche dans lequel les éléments sont connectés par des arcs. """

    def __init__(self, elements=None, arcs=None):
        """
            :param list elements: une liste d'éléments.
            :param list arcs: une liste d'arcs sous forme de tuples\
            ``(element_1, element_2)``.
        """

        self.elements = []
        if elements is not None:
            self.elements = sorted(self.elements, key=lambda e: e.nom)

        self.arcs = []
        if arcs is not None:
            self.ajoute_arcs(arcs)

    def ajoute_arcs(self, arcs):
        """
            Ajoute des arcs à l'espace.

            Les éventuels éléments nouveaux mentionnés dans\
            les arcs seront aussi ajoutés à la liste d'éléments
            de l'espace.

            :param list arcs: une liste d'arcs.
        """
        for element_1, element_2 in arcs:
            if not element_1 in self.elements:
                self.elements.append(element_1)
            if not element_2 in self.elements:
                self.elements.append(element_2)

            if not (element_1, element_2) in self.arcs:
                self.arcs.append((element_1, element_2))

        self.elements = sorted(self.elements, key=lambda e: e.nom)

    def trouve_voisins(self, element):
        """
            :return: les voisins d'un élément.
        """
        voisins = []

        for element_1, element_2 in self.arcs:
            if element_1 == element:
                voisins.append(element_2)
            if element_2 == element:
                voisins.append(element_1)

        voisins = sorted(voisins, key=lambda v: v.nom)

        return voisins

    def __repr__(self):
        rep = ''
        for element in self.elements:
            rep += '{}, '.format(element)
            rep += 'avec voisins: '
            voisins = self.trouve_voisins(element)
            rep += ', '.join(map(str, voisins))
            rep +='\n'

        return rep

Module .../moteurs_recherche/noeud.py

from math import sqrt

class Noeud:
    """ Noeud généré de façon dynamique par un algorithme de recherche,\
        encapsulant un élément de l'espace de recherche.
    """

    def __init__(self, element, parent=None, cout=0, cout_f=0):
        """
            :param Element element: l'élément encapsulé.
            :param Noeud parent: le noeud parent du noeud courant, c'est-à-dire\
            le noeud qui a conduit à la découverte du noeud courant au cours de la\
            recherche.
            :param cout: coût du noeud courant (utile uniquement pour A\*).
            :param cout_f: coût heuristique du noeud courant (utile uniquement\
            pour A\*).
        """
        self.element = element
        self.parent = parent
        self.cout = cout
        self.cout_f = cout_f

    def __repr__(self):
        rep = '<{}, {}, {}>'.format(self.element,
                                   round(self.cout),
                                   round(self.cout_f))
        return rep

Module .../moteurs_recherche/recherche.py

from .noeud import Noeud

class Recherche:
    """ Classe générique pour la recherche. """

    echec = 'échec'

    def __init__(self, espace, optimisee=False):
        """
            :param espace: l'espace de recherche.
            :param optimiser: indique si la recherche doit être optimisée pour éviter\
            les cycles.
        """

        self.espace = espace
        self.optimisee = optimisee

    def recherche(self, depart, but):
        """ Recherche un chemin allant de ``depart`` jusqu'à ``but``.

            :param depart: l'élément de départ.
            :param but: le l'élément but.

            :return: le chemin de ``depart `` à ``but``, ou ``'échec'``\
            s'il n'existe pas de chemin.
        """

        # L'heuristique à utiliser (utile uniquement pour A\*).
        self.h = lambda e: e.distance(but)

        noeud_depart = Noeud(depart, None, 0, self.h(depart))
        noeud_but = Noeud(but)

        return self.recherche_chemin(noeud_depart, noeud_but)

    def recherche_chemin(self, noeud_depart, noeud_but):
        """ Recherche un chemin allant de ``depart`` jusqu'à ``but``.

            :param noeud_depart: le noeud de départ.
            :param noeud_but: le noeud but.

            :return: les éléments encapsulés au long du chemin,\
            ou ``'échec'`` s'il n'existe pas de chemin.
        """
        queue = [noeud_depart]
        iterations = 0
        trace = {}

        while len(queue) > 0:
            noeud = queue.pop(0)

            if self.optimisee and self.detecte_cycle(trace, noeud):
                continue

            iterations += 1
            print('Itération {}: {}'.format(iterations, noeud))

            if noeud.element == noeud_but.element:
                return self.trouve_chemin(noeud)
            else:
                trace[noeud.element] = noeud
                successeurs = self.trouve_successeurs(noeud)
                queue = self.ajoute_successeurs(queue, successeurs)

        return Recherche.echec

    def trouve_chemin(self, noeud):
        chemin = []
        while noeud is not None:
            chemin.insert(0, noeud.element)
            noeud = noeud.parent

        return chemin

    def detecte_cycle(self, trace, noeud):
        """ Vérifie si le noeud courant a déjà été visité par un autre chemin,\
            et donc si l'on vient de parcourir un cycle dans l'espace de recherche.

            :param dict trace: un dictionnaire contenant les noeuds déjà visités ;\
            le dictionnaire associe à chaque élément le noeud qui l'encapsule.\
            Cela permet de tester rapidement si l'élément du noeud courant est\
            présent dans un autre noeud déjà visité.
            :return: True si le noeud courant a déjà été visité.
        """

        return noeud.element in trace

    def trouve_successeurs(self, noeud):
        """ Trouve les successeurs du noeud courant.

            :return: une liste contenant les noeuds successeurs du noeud courant.
        """
        successeurs = []
        voisins = self.espace.trouve_voisins(noeud.element)
        for voisin in voisins:

            # Évite les cycles à deux éléments a - b - a - b ...
            if noeud.parent is not None and noeud.parent.element == voisin:
                continue

            # Coût jusqu'au noeud successeur = coût jusqu'au noeud courant +
            # distance entre les deux noeuds.
            distance = noeud.element.distance(voisin)
            cout = noeud.cout + distance

            # Coût estimé = coût jusqu'au noeud successeur + coût estimé entre
            # le noeud successeur et le but.
            cout_f = cout + self.h(voisin)

            successeur = Noeud(voisin, noeud, cout, cout_f)
            successeurs.append(successeur)

        return successeurs

    def ajoute_successeurs(self, queue, successeurs):
        """ Ajoute les successeurs ``successeurs`` à la queue ``queue``\
            (à implémenter différemment pour DFS, BFS et A\*).

            La queue peut éventuellement être modifiée par la méthode.

            :param list queue: la queue des noeuds à explorer.
            :param list successeurs: les successeurs à ajouter.
            :return: la queue contenant tous les successeurs dans le\
            bon ordre.
        """

        # Nous retournons une liste vide pour éviter de déclencher une exception,
        # mais cette méthode doit être surchargée dans les sous-classes.
        return []

Module .../moteurs_recherche/bfs.py

from .recherche import Recherche
from .noeud import Noeud

class RechercheBFS(Recherche):
    """ Recherche de chemin avec BFS. """

    def ajoute_successeurs(self, queue, successeurs):
        return queue + successeurs

Module .../moteurs_recherche/dfs.py

from .recherche import Recherche
from .noeud import Noeud

class RechercheDFS(Recherche):
    """ Recherche de chemin avec DFS. """

    def ajoute_successeurs(self, queue, successeurs):
        return successeurs + queue

Module .../moteurs_recherche/astar.py

from moteurs_recherche.recherche import Recherche
from moteurs_recherche.noeud import Noeud

class RechercheAStar(Recherche):
    """ Recherche de chemin avec A\*. """

    def detecte_cycle(self, trace, noeud):
        if noeud.element not in trace:
            return False

        autre = trace[noeud.element]
        return autre.cout_f <= noeud.cout_f

    def ajoute_successeurs(self, queue, successeurs):
        queue = queue + successeurs
        queue = sorted(queue, key=lambda n: n.cout_f)

        return queue

Documentation du code

class recherche.moteurs_recherche.element.Element(nom='')

Un élément dans l’espace de recherche.

__init__(nom='')
Paramètres:nom (str) – nom de l’élément (optionnel).
distance(element)

Retourne la distance entre l’éléments courant et un autre.

(A implémenter différemment dans les sous-classes, si nécessaire.)

class recherche.moteurs_recherche.ville.Ville(x, y, nom='')

Bases: recherche.moteurs_recherche.element.Element

Un élément représentant une ville.

Ville.__init__(x, y, nom='')
Paramètres:
  • x – coordonnée en x de la ville.
  • y – coordonnée en y de la ville.
  • nom (str) – nom de la ville.
Ville.distance(ville)

Distance euclidienne entre la ville courante et la ville ville.

class recherche.moteurs_recherche.espace.Espace(elements=None, arcs=None)

Un espace de recherche dans lequel les éléments sont connectés par des arcs.

__init__(elements=None, arcs=None)
Paramètres:
  • elements (list) – une liste d’éléments.
  • arcs (list) – une liste d’arcs sous forme de tuples (element_1, element_2).
ajoute_arcs(arcs)

Ajoute des arcs à l’espace.

Les éventuels éléments nouveaux mentionnés dans les arcs seront aussi ajoutés à la liste d’éléments de l’espace.

Paramètres:arcs (list) – une liste d’arcs.
trouve_voisins(element)
Retourne:les voisins d’un élément.
class recherche.moteurs_recherche.noeud.Noeud(element, parent=None, cout=0, cout_f=0)

Noeud généré de façon dynamique par un algorithme de recherche, encapsulant un élément de l’espace de recherche.

__init__(element, parent=None, cout=0, cout_f=0)
Paramètres:
  • element (Element) – l’élément encapsulé.
  • parent (Noeud) – le noeud parent du noeud courant, c’est-à-dire le noeud qui a conduit à la découverte du noeud courant au cours de la recherche.
  • cout – coût du noeud courant (utile uniquement pour A*).
  • cout_f – coût heuristique du noeud courant (utile uniquement pour A*).
class recherche.moteurs_recherche.recherche.Recherche(espace, optimisee=False)

Classe générique pour la recherche.

__init__(espace, optimisee=False)
Paramètres:
  • espace – l’espace de recherche.
  • optimiser – indique si la recherche doit être optimisée pour éviter les cycles.
ajoute_successeurs(queue, successeurs)

Ajoute les successeurs successeurs à la queue queue (à implémenter différemment pour DFS, BFS et A*).

La queue peut éventuellement être modifiée par la méthode.

Paramètres:
  • queue (list) – la queue des noeuds à explorer.
  • successeurs (list) – les successeurs à ajouter.
Retourne:

la queue contenant tous les successeurs dans le bon ordre.

detecte_cycle(trace, noeud)

Vérifie si le noeud courant a déjà été visité par un autre chemin, et donc si l’on vient de parcourir un cycle dans l’espace de recherche.

Paramètres:trace (dict) – un dictionnaire contenant les noeuds déjà visités ; le dictionnaire associe à chaque élément le noeud qui l’encapsule. Cela permet de tester rapidement si l’élément du noeud courant est présent dans un autre noeud déjà visité.
Retourne:True si le noeud courant a déjà été visité.
recherche(depart, but)

Recherche un chemin allant de depart jusqu’à but.

Paramètres:
  • depart – l’élément de départ.
  • but – le l’élément but.
Retourne:

le chemin de depart `` à ``but, ou 'échec' s’il n’existe pas de chemin.

recherche_chemin(noeud_depart, noeud_but)

Recherche un chemin allant de depart jusqu’à but.

Paramètres:
  • noeud_depart – le noeud de départ.
  • noeud_but – le noeud but.
Retourne:

les éléments encapsulés au long du chemin, ou 'échec' s’il n’existe pas de chemin.

trouve_successeurs(noeud)

Trouve les successeurs du noeud courant.

Retourne:une liste contenant les noeuds successeurs du noeud courant.
class recherche.moteurs_recherche.dfs.RechercheDFS(espace, optimisee=False)

Bases: recherche.moteurs_recherche.recherche.Recherche

Recherche de chemin avec DFS.

RechercheDFS.__init__(espace, optimisee=False)
Paramètres:
  • espace – l’espace de recherche.
  • optimiser – indique si la recherche doit être optimisée pour éviter les cycles.
RechercheDFS.detecte_cycle(trace, noeud)

Vérifie si le noeud courant a déjà été visité par un autre chemin, et donc si l’on vient de parcourir un cycle dans l’espace de recherche.

Paramètres:trace (dict) – un dictionnaire contenant les noeuds déjà visités ; le dictionnaire associe à chaque élément le noeud qui l’encapsule. Cela permet de tester rapidement si l’élément du noeud courant est présent dans un autre noeud déjà visité.
Retourne:True si le noeud courant a déjà été visité.
RechercheDFS.recherche(depart, but)

Recherche un chemin allant de depart jusqu’à but.

Paramètres:
  • depart – l’élément de départ.
  • but – le l’élément but.
Retourne:

le chemin de depart `` à ``but, ou 'échec' s’il n’existe pas de chemin.

RechercheDFS.recherche_chemin(noeud_depart, noeud_but)

Recherche un chemin allant de depart jusqu’à but.

Paramètres:
  • noeud_depart – le noeud de départ.
  • noeud_but – le noeud but.
Retourne:

les éléments encapsulés au long du chemin, ou 'échec' s’il n’existe pas de chemin.

RechercheDFS.trouve_successeurs(noeud)

Trouve les successeurs du noeud courant.

Retourne:une liste contenant les noeuds successeurs du noeud courant.
class recherche.moteurs_recherche.bfs.RechercheBFS(espace, optimisee=False)

Bases: recherche.moteurs_recherche.recherche.Recherche

Recherche de chemin avec BFS.

RechercheBFS.__init__(espace, optimisee=False)
Paramètres:
  • espace – l’espace de recherche.
  • optimiser – indique si la recherche doit être optimisée pour éviter les cycles.
RechercheBFS.detecte_cycle(trace, noeud)

Vérifie si le noeud courant a déjà été visité par un autre chemin, et donc si l’on vient de parcourir un cycle dans l’espace de recherche.

Paramètres:trace (dict) – un dictionnaire contenant les noeuds déjà visités ; le dictionnaire associe à chaque élément le noeud qui l’encapsule. Cela permet de tester rapidement si l’élément du noeud courant est présent dans un autre noeud déjà visité.
Retourne:True si le noeud courant a déjà été visité.
RechercheBFS.recherche(depart, but)

Recherche un chemin allant de depart jusqu’à but.

Paramètres:
  • depart – l’élément de départ.
  • but – le l’élément but.
Retourne:

le chemin de depart `` à ``but, ou 'échec' s’il n’existe pas de chemin.

RechercheBFS.recherche_chemin(noeud_depart, noeud_but)

Recherche un chemin allant de depart jusqu’à but.

Paramètres:
  • noeud_depart – le noeud de départ.
  • noeud_but – le noeud but.
Retourne:

les éléments encapsulés au long du chemin, ou 'échec' s’il n’existe pas de chemin.

RechercheBFS.trouve_successeurs(noeud)

Trouve les successeurs du noeud courant.

Retourne:une liste contenant les noeuds successeurs du noeud courant.
class recherche.moteurs_recherche.astar.RechercheAStar(espace, optimisee=False)

Bases: moteurs_recherche.recherche.Recherche

Recherche de chemin avec A*.

RechercheAStar.__init__(espace, optimisee=False)
Paramètres:
  • espace – l’espace de recherche.
  • optimiser – indique si la recherche doit être optimisée pour éviter les cycles.
RechercheAStar.recherche(depart, but)

Recherche un chemin allant de depart jusqu’à but.

Paramètres:
  • depart – l’élément de départ.
  • but – le l’élément but.
Retourne:

le chemin de depart `` à ``but, ou 'échec' s’il n’existe pas de chemin.

RechercheAStar.recherche_chemin(noeud_depart, noeud_but)

Recherche un chemin allant de depart jusqu’à but.

Paramètres:
  • noeud_depart – le noeud de départ.
  • noeud_but – le noeud but.
Retourne:

les éléments encapsulés au long du chemin, ou 'échec' s’il n’existe pas de chemin.

RechercheAStar.trouve_successeurs(noeud)

Trouve les successeurs du noeud courant.

Retourne:une liste contenant les noeuds successeurs du noeud courant.