web-dev-qa-db-fra.com

Récursivité: comment éviter Python set a changé de set pendant l'itération RuntimeError

Contexte et description du problème:

J'ai du code qui résout le problème de coloration du graphe (largement défini comme le problème de l'attribution de "couleurs" à un graphe non orienté, en veillant à ce que deux sommets connectés par un Edge n'aient pas la même couleur). J'essaie d'implémenter une solution utilisant la propagation de contraintes pour améliorer l'efficacité d'un algorithme de retour arrière récursif standard, mais je rencontre l'erreur suivante:

  File "C:\Users\danisg\Desktop\coloring\Solver.py", 
  line 99, in solve
  for color in self.domains[var]:
  RuntimeError: Set changed size during iteration

Ici, pour chaque sommet, je garde un set de valeurs particulières possibles pour ce sommet particulier:

  self.domains = { var: set(self.colors) for var in self.vars }

Après avoir effectué une affectation, je propage cette contrainte aux domaines voisins, pour limiter l'espace de recherche:

  for key in node.neighbors:          # list of keys corresponding to adjacent vertices
      if color in self.domains[key]:  # remove now to Prune possible choices
          self.domains[key].remove(color)

Ce n'est pas là que l'erreur réelle est levée (dans mon code, j'indique où se trouve le problème dans un try-except block), mais peut être à l'origine du problème.

Ma question:

Ai-je la bonne idée, ici, sinon la bonne mise en œuvre? Plus précisément, comment puis-je résoudre ce problème? Est-il également nécessaire de conserver un dictionnaire domains distinct? Ou pourrions-nous faire de domain une propriété de chaque nœud du graphique?

Mon code:

Voici la fonction solve où ce code est appelé:

def solve(self):

    uncolored = [var for var in self.vars if self.map[var].color == None]
    if len(uncolored) == 0:
        return True

    var  = min(uncolored, key = lambda x: len(self.domains[var]))
    node = self.map[var]
    old  = { var: set(self.domains[var]) for var in self.vars }

    for color in self.domains[var]:

        if not self._valid(var, color):
            continue


        self.map[var].color = color
        for key in node.neighbors:

            if color in self.domains[key]:
                self.domains[key].remove(color)

        try:
            if self.solve():
                return True
        except:
            print('happening now')


        self.map[var].color = None
        self.domains = old


    return False

Mon implémentation utilise un objet Node:

class Solver:

    class Node:

        def __init__(self, var, neighbors, color = None, domain = set()):

            self.var       = var
            self.neighbors = neighbors
            self.color     = color
            self.domain    = domain

        def __str__(self):
            return str((self.var, self.color))



    def __init__(self, graph, K):

        self.vars    = sorted( graph.keys(), key = lambda x: len(graph[x]), reverse = True )  # sort by number of links; start with most constrained
        self.colors  = range(K)
        self.map     = { var: self.Node(var, graph[var]) for var in self.vars }
        self.domains = { var: set(self.colors)           for var in self.vars }

Voici deux autres fonctions utilisées/utiles:

def validate(self):

    for var in self.vars:
        node = self.map[var]

        for key in node.neighbors:
            if node.color == self.map[key].color:
                return False

    return True

def _valid(self, var, color):

    node = self.map[var]

    for key in node.neighbors:

        if self.map[key].color == None:
            continue

        if self.map[key].color == color:
            return False

    return True

Données et exemple pour lesquels le code échoue:

Le graphique d'exemple que j'utilise peut être trouvé ici .

La fonction de lecture des données:

def read_and_make_graph(input_data):

    lines = input_data.split('\n')

    first_line = lines[0].split()
    node_count = int(first_line[0])
    Edge_count = int(first_line[1])

    graph = {}
    for i in range(1, Edge_count + 1):
        line  = lines[i]
        parts = line.split()
        node, Edge = int(parts[0]), int(parts[1])

        if node in graph:
            graph[node].add(Edge)

        if Edge in graph:
            graph[Edge].add(node)

        if node not in graph:
            graph[node] = {Edge}

        if Edge not in graph:
            graph[Edge] = {node}

    return graph

Il devrait être appelé comme suit:

file_location = 'C:\\Users\\danisg\\Desktop\\coloring\\data\\gc_50_3'
input_data_file = open(file_location, 'r')
input_data = ''.join(input_data_file.readlines())
input_data_file.close()

graph  = read_and_make_graph(input_data)
solver = Solver(graph, 6)  # a 6 coloring IS possible

print(solver.solve())      # True if we solved; False if we didn't
25
rookie

Je pense que le problème est ici:

for color in self.domains[var]:

    if not self._valid(var, color):
        continue

    self.map[var].color = color
    for key in node.neighbors:

        if color in self.domains[key]:
            self.domains[key].remove(color)  # This is potentially bad.

si key == var lorsque self.domains[key].remove(color) est appelée, vous changez la taille de l'ensemble sur lequel vous êtes en train d'itérer. Vous pouvez éviter cela en utilisant

for color in self.domains[var].copy():

L'utilisation de copy () vous permettra d'itérer sur une copie de l'ensemble, tout en supprimant des éléments de l'original.

43
dano