Обход всех доступных путей в диаграмме - PullRequest
0 голосов
/ 05 июня 2018

Существует графовая структура с числами, как показано ниже.

enter image description here

Чтобы загрузить эту структуру в графическом объекте в python.Я сделал это в виде многострочной строки, как показано ниже.

myString='''1
2 3
4 5 6
7 8 9 10
11 12 13 14 15'''

Представление ее в виде списка списков.

>>> listofLists=[ list(map(int,elements.split())) for elements in myString.strip().split("\n")]
>>> print(listofLists)
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10], [11, 12, 13, 14, 15]]

создание структуры графа с использованием приведенного ниже узла Node и класса ребра вpython

Класс узла, для него требуется позиция в качестве кортежа и значения Пример: элементы и его позиция, значение

1 --- position (0,0) and value is 1
2 --- position (1,0) and value is 2
3 --- position (1,1) and value is 3

Класс узла

class node(object):
    def __init__(self,position,value):
        '''
        position : gives the position of the node wrt to the test string as a tuple
        value    : gives the value of the node
        '''
        self.value=value
        self.position=position

    def getPosition(self):
        return self.position

    def getvalue(self):
        return self.value

    def __str__(self):
        return 'P:'+str(self.position)+' V:'+str(self.value)

Реброкласс создает ребро между двумя узлами.

class edge(object):
    def __init__(self,src,dest):
        '''src and dest are nodes'''
        self.src = src
        self.dest = dest

    def getSource(self):
        return self.src

    def getDestination(self):
        return self.dest
    #return the destination nodes value as the weight
    def getWeight(self):
        return self.dest.getvalue()

    def __str__(self):
        return (self.src.getPosition(),)+'->'+(self.dest.getPosition(),)

Класс направленного графа такой, как показано ниже.Структура графа строится как список смежности.{узел1: [узел2, узел3], узел2: [узел3, узел4] ......}

class Diagraph(object):

    '''the edges is a dict mapping node to a list of its destination'''
    def __init__(self):
        self.edges = {}

    '''Adds the given node as a key to the dict named edges ''' 
    def addNode(self,node):
        if node in self.edges:
            raise ValueError('Duplicate node')
        else:
            self.edges[node]=[]

    '''addEdge accepts and edge class object checks if source and destination node are present in the graph '''     
    def addEdge(self,edge):
        src = edge.getSource()
        dest = edge.getDestination()
        if not (src in self.edges and dest in self.edges):
            raise ValueError('Node not in graph')
        self.edges[src].append(dest)

    '''getChildrenof returns  all the children of the node'''   
    def getChildrenof(self,node):
        return self.edges[node]

    '''to check whether a node is present in the graph or not'''    
    def hasNode(self,node):
        return node in self.edges

    '''rootNode returns the root node i.e node at position (0,0)''' 
    def rootNode(self):
        for  keys in self.edges:
            return keys if keys.getPosition()==(0,0) else 'No Root node for this graph'

Функция для создания и возврата графического объекта, с которым нужно работать.

def createmygraph(testString):
    '''input is a multi-line string'''

    #create a list of lists from the string
    listofLists=[ list(map(int,elements.split())) for elements in testString.strip().split("\n")]
    y = Diagraph()
    nodeList = []

    # create all the nodes and store it in a list nodeList
    for i in range(len(listofLists)):
        for j in range(len(listofLists)):
            if i<=j:
                mynode=node((j,i),listofLists[j][i])
                nodeList.append(mynode)
                y.addNode(mynode)

    # create all the edges
    for srcNode in nodeList:
    # iterate through all the nodes again and form a logic add the edges
        for destNode in nodeList:
            #to add the immediate down node eg : add 7 (1,0) to 3 (0,0) , add 2 (2,0) to 7 (1,0)
            if srcNode.getPosition()[0]==destNode.getPosition()[0]-1 and srcNode.getPosition()[1]==destNode.getPosition()[1]-1:
                y.addEdge(edge(srcNode,destNode))
            #to add the bottom right node eg :add 4 (1,1) to 3 (0,0) 
            if srcNode.getPosition()[0]==destNode.getPosition()[0]-1 and srcNode.getPosition()[1]==destNode.getPosition()[1]:
                y.addEdge(edge(srcNode,destNode))

    return y

Как перечислить все пути, доступные между двумя узлами. В частности, между 1 ----> 11, 1 ----> 12, 1 ----> 13, 1---> 14, 1 ----> 15 В этом случае я попытался использовать подход первой глубины слева.Но он не может получить пути.

def leftFirstDepthFirst(graph,start,end,path,valueSum):
    #add input start node to the path
    path=path+[start]
    #add the value to the valueSum variable
    valueSum+=start.getvalue()
    print('Current Path ',printPath(path))
    print('The sum is ',valueSum)
    # return if start and end node matches.
    if start==end:
        print('returning as start and end have matched')
        return path

    #if there are no further destination nodes, move up a node in the path and remove the current element from the path list.
    if not graph.getChildrenof(start):
        path.pop()
        valueSum=valueSum-start.getvalue()
        return leftFirstDepthFirst(graph,graph.getChildrenof(path[-1])[1],end,path,valueSum)
    else:
        for aNode in graph.getChildrenof(start):
            return leftFirstDepthFirst(graph,aNode,end,path,valueSum)
    print('no further path to explore')

Тестирование кода.

#creating a graph object with given string
y=createmygraph(myString)

функция для возврата терминальных узлов, таких как 11,12,13,14,15.

def fetchTerminalNode(graph,position):
    terminalNode=[]
    for keys in graph.edges:
        if not graph.edges[keys]:
            terminalNode.append(keys)
    return terminalNode[position]

Запуск глубины сначала слева первой функции.

source=y.rootNode() # element at position (0,0)
destination=fetchTerminalNode(y,1) #ie. number 12
print('Path from ',start ,'to ',destination)
xyz=leftFirstDepthFirst(y,source,destination,[],0)

Путь выбирается для элементов 11 и 12, но не для 13, 14 или 15. Т.е. destination=fetchTerminalNode(y,2) не работает. Можетпожалуйста, кто-нибудь предложить способ решения этой проблемы.

Ответы [ 2 ]

0 голосов
/ 15 июня 2018

PFB функция, которую я мог придумать, которая использует дыхание первого поиска для печати всех доступных путей между корневым узлом и конечными узлами.

Google Colab / github ссылка

def breathfirstalgo(graph,tempPaths,finalPath):
## iterates over all the lists inside the tempPaths and checks if there are child nodes to its last node.
condList=[graph.getChildrenof(apartList[-1]) for apartList in tempPaths if graph.getChildrenof(apartList[-1])]

tempL=[]    
if condList:

    for partialList in tempPaths:
        #get the children of the last element of partialList
        allchild=graph.getChildrenof(partialList[-1])

        if allchild:
            noOfChild=len(allchild)
            #create noOfChild copies of the partialList
            newlist=[partialList[:] for _ in range(noOfChild)]      
            #append the a child element to the new list
            for i in range(noOfChild):
                newlist[i].append(allchild[i])

            #append each list to the temp list tempL
            for alist in newlist:
                tempL.append(alist)

        else:
            pass

    #after completion of the for loop i.e iterate through 1 level
    return breathfirstalgo(graph,tempL,finalPath)
else:
    #append all the lists from tempPaths to finalPath that will be returned
    for completePath in tempPaths:
        finalPath.append(completePath)
    return finalPath

Тестирование решения дыхания первого поиска, как показано ниже.

mygraph=createmygraph(myString)
print('The graph object is ',mygraph)
print('The root node is ',mygraph.rootNode())
#print(mygraph)
all_list=breathfirstalgo(mygraph,tempPaths=[[mygraph.rootNode()]],finalPath=[])

print('alllist is ')
for idx,partlist in enumerate(all_list):
    print(printPath(partlist))

Вывод такой же, как показано ниже, после изменения __str__ класса узла как str(self.value)

The graph object is  <__main__.Diagraph object at 0x7f08e5a3d128>
The root node is  1
alllist is 
-->1-->2-->4-->7-->11
-->1-->2-->4-->7-->12
-->1-->2-->4-->8-->12
-->1-->2-->4-->8-->13
-->1-->2-->5-->8-->12
-->1-->2-->5-->8-->13
-->1-->2-->5-->9-->13
-->1-->2-->5-->9-->14
-->1-->3-->5-->8-->12
-->1-->3-->5-->8-->13
-->1-->3-->5-->9-->13
-->1-->3-->5-->9-->14
-->1-->3-->6-->9-->13
-->1-->3-->6-->9-->14
-->1-->3-->6-->10-->14
-->1-->3-->6-->10-->15
0 голосов
/ 05 июня 2018

Учитывая tree

tree = \
  [ [1]
  , [2, 3]
  , [4, 5, 6]
  , [7, 8, 9, 10]
  , [11, 12, 13, 14, 15]
  ]

И traverse функцию

def traverse (tree):
  def loop (path, t = None, *rest):
    if not rest:
      for x in t:
        yield path + [x]
    else:
      for x in t:
        yield from loop (path + [x], *rest)
  return loop ([], *tree)

Пройти все пути ...

for path in traverse (tree):
  print (path)

# [ 1, 2, 4, 7, 11 ]
# [ 1, 2, 4, 7, 12 ]
# [ 1, 2, 4, 7, 13 ]
# [ 1, 2, 4, 7, 14 ]
# [ 1, 2, 4, 7, 15 ]
# [ 1, 2, 4, 8, 11 ]
# [ 1, 2, 4, 8, 12 ]
# ...
# [ 1, 3, 6, 9, 15 ]
# [ 1, 3, 6, 10, 11 ]
# [ 1, 3, 6, 10, 12 ]
# [ 1, 3, 6, 10, 13 ]
# [ 1, 3, 6, 10, 14 ]
# [ 1, 3, 6, 10, 15 ]

Или собрать всепутей в списке

print (list (traverse (tree)))
# [ [ 1, 2, 4, 7, 11 ]
# , [ 1, 2, 4, 7, 12 ]
# , [ 1, 2, 4, 7, 13 ]
# , [ 1, 2, 4, 7, 14 ]
# , [ 1, 2, 4, 7, 15 ]
# , [ 1, 2, 4, 8, 11 ]
# , [ 1, 2, 4, 8, 12 ]
# , ...
# , [ 1, 3, 6, 9, 15 ]
# , [ 1, 3, 6, 10, 11 ]
# , [ 1, 3, 6, 10, 12 ]
# , [ 1, 3, 6, 10, 13 ]
# , [ 1, 3, 6, 10, 14 ]
# , [ 1, 3, 6, 10, 15 ]
# ]

Выше мы использовали генераторы, которые являются высокоуровневой функцией в Python.Может быть, вы хотите понять, как реализовать решение с использованием более примитивных функций ...

Общий механизм, который мы ищем здесь, - это монада списка, которая отражает идею неоднозначных вычислений;некоторая процедура, которая может потенциально возвратить более чем одно значение.

Python уже предоставляет списки и способ их построения с использованием [].Нам нужно только предоставить операцию привязки с именем flat_map ниже

def flat_map (f, xs):
  return [ y for x in xs for y in f (x) ]

def traverse (tree):
  def loop (path, t = None, *rest):
    if not rest:
      return map (lambda x: path + [x], t)
    else:
      return flat_map (lambda x: loop (path + [x], *rest), t)
  return loop ([], *tree)

print (traverse (tree))
# [ [ 1, 2, 4, 7, 11 ]
# , [ 1, 2, 4, 7, 12 ]
# , [ 1, 2, 4, 7, 13 ]
# , ... same output as above ...
# ]

Oh, а в Python есть встроенная функция product, которая работает точно так, как нам нужно.Разница лишь в том, что пути будут выводиться в виде кортежей () вместо списков []

from itertools import product

tree = \
  [ [1]
  , [2, 3]
  , [4, 5, 6]
  , [7, 8, 9, 10]
  , [11, 12, 13, 14, 15]
  ]

for path in product (*tree):
  print (path)

# (1, 2, 4, 7, 11)
# (1, 2, 4, 7, 12)
# (1, 2, 4, 7, 13)
# (1, 2, 4, 7, 14)
# (1, 2, 4, 7, 15)
# ... same output as above ...

В вашей программе вы пытаетесь абстрагировать этот механизм с помощью различных классов, node и edgeи diagraph.В конечном итоге вы можете структурировать свою программу так, как вам хочется, но знайте, что не нужно, чтобы было более сложным, чем мы ее здесь написали.


Обновление

Как указывает @ user3386109 в комментариях, моя программа выше генерирует пути, как если бы каждый родитель был подключен к всем дочерним элементам.Однако это ошибка, поскольку ваш график показывает, что родители подключены только к смежным детям.Мы можем исправить это с помощью модификации нашей программы - изменения ниже полужирный

def traverse (tree):
  def loop (path, <b>i,</b> t = None, *rest):
    if not rest:
      for <b>(j,</b>x<b>)</b> in <b>enumerate (</b>t<b>)</b>:
        <b>if i == j or i + 1 == j:</b>
          yield path + [x]
    else:
      for <b>(j,</b>x<b>)</b> in <b>enumerate (</b>t<b>)</b>:
        <b>if i == j or i + 1 == j:</b>
          yield from loop (path + [x], <b>j,</b> *rest)
  return loop ([], <b>0,</b> *tree)

Выше мы используем индексы i и j, чтобы определить, какие узлы являются "смежными"но это загромождало нашу loop кучу.Кроме того, новый код выглядит так, как будто вводит некоторое дублирование.Придавая этому намерению имя adjacencies, мы сохраняем чистоту нашей функции

def traverse (tree):
  <b>def adjacencies (t, i):
    for (j, x) in enumerate (t):
      if i == j or i + 1 == j:
        yield (j, x)</b>

  def loop (path, i, t = None, *rest):
    if not rest:
      for <b>(_, x)</b> in <b>adjacencies (t, i)</b>:
        yield path + [x]
    else:
      for <b>(j, x)</b> in <b>adjacencies (t, i)</b>:
        yield from loop (path + [x], j, *rest)

  return loop ([], 0, *tree)

То же самое, но на этот раз мы получим результат, указанный в исходном вопросе

for path in traverse (tree):
  print (path)

# [1, 2, 4, 7, 11]
# [1, 2, 4, 7, 12]
# [1, 2, 4, 8, 12]
# [1, 2, 4, 8, 13]
# [1, 2, 5, 8, 12]
# [1, 2, 5, 8, 13]
# [1, 2, 5, 9, 13]
# [1, 2, 5, 9, 14]
# [1, 3, 5, 8, 12]
# [1, 3, 5, 8, 13]
# [1, 3, 5, 9, 13]
# [1, 3, 5, 9, 14]
# [1, 3, 6, 9, 13]
# [1, 3, 6, 9, 14]
# [1, 3, 6, 10, 14]
# [1, 3, 6, 10, 15]

ПричинаЭта простая функция adjancies работает потому, что ваши входные данные единообразны и действительны.Вы можете четко визуализировать индексы i и i + 1, используя цветовое кодирование путей в вашем изображении.Нам никогда не нужно беспокоиться об ошибке индекса за пределами границ, поскольку вы можете видеть, что i + 1 никогда не вычисляется на узлах без дочерних элементов (т. Е. В последней строке).Если вы указали неверные данные, traverse не гарантирует действительный результат.

paths as indices

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...