Я работаю над проектом графической модели с использованием Python, используя NetworkX . NetworkX обеспечивает простую и хорошую функциональность, используя словари:
import networkx as nx
G = nx.DiGraph() # a directed graph
G.add_edge('a', 'b')
print G['a'] # prints {'b': {}}
print G['b'] # prints {}
Я хочу использовать ориентированные графы, потому что я кодирую зависимости, у которых есть направления (в приведенном выше примере у меня есть закрытая форма для 'b', условная для 'a', а не наоборот).
Для данного узла я хочу найти предшественников этого узла. Для приведенного выше примера par ('b') должен вернуть ['a']. У NetworkX есть функция-преемник, которая находит дочерние элементы любого узла. Очевидно, что, пройдя все узлы и найдя те, которые имеют «b» в качестве дочернего элемента, будет работать, но это будет Ω (n) по числу узлов (что будет слишком дорого для моего приложения).
Я не могу представить, что что-то такое простое было бы исключено из этой хорошо сделанной упаковки, но ничего не могу найти.
Одним из эффективных вариантов является сохранение направленной и ненаправленной версии графика; все неориентированные ребра, по существу, реализуются путем добавления обоих направленных ребер, и поэтому можно было бы принять заданное различие между соседними узлами и дочерними элементами (которые были бы предшественником).
Беда в том, что я не уверен в самом питоническом способе обернуть существующие классы Networkx DiGraph и Graph для достижения этой цели. На самом деле я просто хочу получить класс PGraph, который ведет себя точно так же, как класс DiGraph networkx, но имеет функцию predecessors(node)
в дополнение к функции successors(node)
.
Должно ли PGraph наследовать от DiGraph и инкапсулировать Graph (для использования в функции предшественников)? Как тогда я должен заставить все узлы и ребра быть добавленными как к ориентированным, так и к ненаправленным графам, которые он содержит? Должен ли я просто переопределить функции для добавления и удаления узлов и ребер в PGraph (чтобы они добавлялись и удалялись как из направленной, так и ненаправленной версии)? Я беспокоюсь о том, что если я что-то упущу из виду, то позже у меня начнется головная боль, которая может не означать хорошего дизайна.
Или (и, пожалуйста, пусть это будет True
), существует ли простой способ получить предшественников узла в сети x.DiGraph, и я полностью пропустил это?
Большое спасибо за вашу помощь.
EDIT:
Я думаю, что это делает работу. PGraph наследует от DiGraph и инкапсулирует другой DiGraph (этот перевернутый). Я переопределил методы для добавления и удаления узлов и ребер.
import networkx as nx
class PGraph(nx.DiGraph):
def __init__(self):
nx.DiGraph.__init__(self)
self.reversed_graph = nx.DiGraph()
def add_node(self, n, attr_dict=None, **attr):
nx.DiGraph.add_node(self, n, attr_dict, **attr)
self.reversed_graph.add_node(n, attr_dict, **attr)
def add_nodes_from(self, ns, attr_dict=None, **attr):
nx.DiGraph.add_nodes_from(self, ns, attr_dict, **attr)
self.reversed_graph.add_nodes_from(ns, attr_dict, **attr)
def add_edge(self, a, b, attr_dict=None, **attr):
nx.DiGraph.add_edge(self, a, b, attr_dict, **attr)
self.reversed_graph.add_edge(b, a, attr_dict, **attr)
def add_edges_from(self, es, attr_dict=None, **attr):
nx.DiGraph.add_edges_from(self, es, attr_dict, **attr)
self.reversed_graph.add_edges_from(es, attr_dict, **attr)
def remove_node(self, n):
nx.DiGraph.remove_node(self, n)
self.reversed_graph.remove_node(n)
def remove_nodes_from(self, ns):
nx.DiGraph.remove_nodes_from(self, ns)
self.reversed_graph.remove_nodes_from(ns)
def remove_edge(self, a, b):
nx.DiGraph.remove_edge(self, b, a)
self.reversed_graph.remove_edge(a, b)
def remove_edges_from(self, es):
nx.DiGraph.remove_edges_from(self, es)
self.reversed_graph.remove_edges_from([ (b,a) for a,b in es])
# the predecessors function I wanted
def predecessors(self, n):
return self.reversed_graph.successors(n)
Что вы думаете об этом решении? Это может удвоить использование памяти, но я думаю, что это приемлемо. Это слишком сложно? Это хороший дизайн?