Nano Hash - криптовалюты, майнинг, программирование

Получить возможные пути

У меня есть простая структура данных, показывающая узлы в ориентированном графе:

{
    'node1': [('V1', 'R1')],
    'node2': [('R1', 'R2'), ('R1', 'R3')],
    'node3': [('R2', 'R4'), ('R2', 'R5'), ('R3', 'R4'), ('R3', 'R5')],
    'node4': [('R4', 'Z1')],
    'node5': [('R5', 'Z1')]
}

Я хотел бы получить все возможные (направленные) пути от V1 до Z. Например, путь может быть:

[
    ('V1', 'R1'),
    ('R1', 'R2'),
    ('R2', 'R4'),
    ('R4', 'Z1')
]

Тем не менее, у меня возникли проблемы с тем, что кажется базовым алгоритмом, который, как мне кажется, включает рекурсию.

for node, connections in nodes.items():
    for connection in connections:

Я начал с чего-то вроде вышеизложенного, но я думаю, что это неправильный подход. Каким будет предложенный способ сделать это, не используя что-то вроде itertools ?


  • Каков размер самого большого графа, на котором это должно работать? (рекурсия может быть плохой идеей для огромных графов) Будут ли (возможно) петли в вашем ориентированном графе? 02.02.2020
  • @Grismar очень маленький. Это игрушечный график, а не реальное приложение. 02.02.2020
  • Кроме того, есть ли особое значение для группировки в узлах? Потому что кажется, что кортежи действительно обозначают ребра между узлами, такими как V1 и R1, а что-то вроде node1 - это просто произвольная группа? 02.02.2020
  • @Grismar нет - просто удобство для возможности читать и видеть разные узлы (и рисовать их) немного проще. 02.02.2020
  • Я думаю, что именование «узлов» на самом деле может немного сбивать с толку, но, по крайней мере, это не должно влиять на ответ. Это сбивает с толку, потому что то, что называется «узлом» в данных вашего примера, на самом деле (в теории графов) не является «узлом». 02.02.2020
  • @ Грисмар - ты прав. Я перепутал их, это должно быть что-то вроде соединений, поскольку оно просто дает список ребер в определенной «точке соединения». Узел будет чем-то вроде V1, который я перепутал выше. Спасибо что подметил это. 06.02.2020

Ответы:


1

Учитывая, что кортежи в структуре данных — это ребра, а значения в кортежах — это узлы графа, можно реорганизовать данные таким образом, чтобы упростить алгоритм:

graph = [edge for es in source.values() for edge in es]

Поскольку в графе могут быть циклы, нам нужно отслеживать узлы, которые уже были посещены. Имея это в виду, рекурсивная функция, находящая все пути от начального узла к конечному узлу, дает граф в виде списка ребер от узла к узлу:

def find_path(start, end, edges, visited=None):
    if visited is None:
        visited = []
    for n1, n2, in edges:
        if n1 == start:
            if n2 == end:
                yield [n1, n2]
            elif n2 not in visited:
                for continuation in find_path(n2, end, edges, visited + [n1]):
                    yield [n1] + continuation

Все это:

source = {
    'node1': [('V1', 'R1')],
    'node2': [('R1', 'R2'), ('R1', 'R3')],
    'node3': [('R2', 'R4'), ('R2', 'R5'), ('R3', 'R4'), ('R3', 'R5')],
    'node4': [('R4', 'Z1')],
    'node5': [('R5', 'Z1')]
}

graph = [edge for es in source.values() for edge in es]


def find_path(start, end, edges, visited=None):
    if visited is None:
        visited = []
    for n1, n2, in edges:
        if n1 == start:
            if n2 == end:
                yield [n1, n2]
            elif n2 not in visited:
                for continuation in find_path(n2, end, edges, visited + [n1]):
                    yield [n1] + continuation


print(list(find_path('V1', 'Z1', graph)))

Вывод:

[['V1', 'R1', 'R2', 'R4', 'Z1'], ['V1', 'R1', 'R2', 'R5', 'Z1'], ['V1', 'R1', 'R3', 'R4', 'Z1'], ['V1', 'R1', 'R3', 'R5', 'Z1']]

Обратите внимание, что результат приводится к списку, потому что функция является генератором, она выдает решения по одному. Вызов list() собирает все результаты в одном выводе.

02.02.2020
  • еще раз спасибо за этот ответ. Немного изучив это и попрактиковавшись в рекурсии, я придумал свое собственное решение ниже, которое является немного «более простым» и понятным для меня (т. Е. Оно не использует yield). Не могли бы вы взглянуть и сообщить мне, похоже ли, что я на правильном пути? 06.02.2020

  • 2

    Вы можете использовать рекурсию с генератором:

    data = {'node1': [('V1', 'R1')], 'node2': [('R1', 'R2'), ('R1', 'R3')], 'node3': [('R2', 'R4'), ('R2', 'R5'), ('R3', 'R4'), ('R3', 'R5')], 'node4': [('R4', 'Z1')], 'node5': [('R5', 'Z1')]}
    new_data = [i for b in data.values() for i in b]
    def lookup(start, end, seen=[], c = []):
       _r = [(a, b) for a, b in new_data if a == start and a not in seen]
       for a, b in _r:
          if b == end:
             yield c+[(a, b)]
          else:
             yield from lookup(b, end, seen=seen+[start], c=c+[(a, b)])
    
    print(list(lookup('V1', 'Z1')))
    

    Вывод:

    [
      [('V1', 'R1'), 
       ('R1', 'R2'), 
       ('R2', 'R4'), 
       ('R4', 'Z1')], 
      [('V1', 'R1'),  
       ('R1', 'R2'), 
       ('R2', 'R5'), 
       ('R5', 'Z1')], 
      [('V1', 'R1'), 
       ('R1', 'R3'), 
       ('R3', 'R4'), 
       ('R4', 'Z1')], 
      [('V1', 'R1'), 
       ('R1', 'R3'), 
       ('R3', 'R5'), 
       ('R5', 'Z1')]
    ]
    
    02.02.2020
  • спасибо, это очень прикольно. Не могли бы вы немного объяснить свой код. Например, зачем вам нужно определять new_data и что такое c? 02.02.2020
  • @ David542 Ваши исходные входные данные содержали ключи node, которые (с моей точки зрения) не имеют реального отношения к задаче поиска пути. new_data хранит все пары кортежей из data. c — это текущий список всех собранных путей, yieldотображаемый обратно, когда найден нужный конечный узел. 02.02.2020

  • 3

    Следующее решение гораздо менее элегантно и более многословно, чем два других решения, но вот пример реализации, расширяющий различные функции:

    def flatten_list(l, out=None):
        """
        Flatten to get a list of all edges:
    
        in:  [[('V1', 'R1')], [('R1', 'R2'), ('R1', 'R3')]
        out: [('V1', 'R1'), ('R1', 'R2'), ('R1', 'R3')]
        """
        if out is None: out=[]
        for li in l:
            if not isinstance(li, list):
                out.append(li)
            else:
                flatten_list(li, out)
        return out
    
    
    def get_connected_nodes_from(list_of_edges, from_node):
        """
        Given an input node (string), and list of edges (tuple),
        Return a list of all nodes (list of strings) connected to the input node.
        Note: this is a directed graph. That is, we are only grabbing descendants
              and not all (undirected) edges.
    
        in:  from_node='R1', list_of_edges=[('V1', 'R1'), ('R1', 'R2'), ('R1', 'R3')]
        out: ['R2', 'R3']
        """
        out = []
        for edge in list_of_edges:
            if edge[0] == from_node:
                out.append(edge[1])
            elif from_node == edge[0]:
                out.append(edge[0])
        return out
    
    
    def get_all_paths(list_of_edges, node=None, current_path=None, all_paths=None):
        """
        Given a list of edges, this will return all directed paths from start to finish.
        """
        # "Initialize" things on the first time through
        if all_paths is None: all_paths = []; node = list_of_edges[0][0]; current_path = [node,]
        node_descendants = get_connected_nodes_from(list_of_edges, node) 
        if len(node_descendants) == 0:
            all_paths.append(current_path) # append the path when it is a leaf with no descendants
        else:
            [get_all_paths(list_of_edges, node, current_path + [node,], all_paths) for node in node_descendants]
        return all_paths
    

    И используя его:

    >>> graph = {
        'node1': [('V1', 'R1')],
        'node2': [('R1', 'R2'), ('R1', 'R3')],
        'node3': [('R2', 'R4'), ('R2', 'R5'), ('R3', 'R4'), ('R3', 'R5')],
        'node4': [('R4', 'Z1')],
        'node5': [('R5', 'Z1')],
    }
    >>> list_of_edges = flatten_list(graph.values())
    >>> print (['-->'.join(path) for path in get_all_paths(list_of_edges)])
    # ['V1-->R1-->R2-->R4-->Z1', 'V1-->R1-->R2-->R5-->Z1', 'V1-->R1-->R3-->R4-->Z1', 'V1-->R1-->R3-->R5-->Z1']
    
    
    
    05.02.2020
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..