问题描述
|
我正在根据sqlAlchemy实现一种具有部分排序集数学特征的结构,在该结构中,我需要能够一次添加和删除边。
在我目前的最佳设计中,我使用两个邻接列表,一个是分配列表(在Hass图中大约是边),因为我需要保留哪些节点对已明确设置为有序,而另一个邻接列表是可传递的首先关闭,以便我可以高效地查询一个节点是否相对于另一个节点排序。现在,每次在分配邻接列表中添加或删除边时,我都会重新计算可传递闭包。
看起来像这样:
assignment = Table(\'assignment\',Metadata,Column(\'parent\',Integer,ForeignKey(\'node.id\')),Column(\'child\',ForeignKey(\'node.id\')))
closure = Table(\'closure\',Column(\'ancestor\',Column(\'descendent\',ForeignKey(\'node.id\')))
class Node(Base):
__tablename__ = \'node\'
id = Column(Integer,primary_key=True)
parents = relationship(Node,secondary=assignment,backref=\'children\',primaryjoin=id == assignment.c.parent,secondaryjoin=id == assignment.c.child)
ancestors = relationship(Node,secondary=closure,backref=\'descendents\',primaryjoin=id == closure.c.ancestor,secondaryjoin=id == closure.c.descendent,viewonly=True)
@classmethod
def recompute_ancestry(cls.conn):
conn.execute(closure.delete())
adjacent_values = conn.execute(assignment.select()).fetchall()
conn.execute(closure.insert(),floyd_warshall(adjacent_values))
其中“ 1”是同名算法的实现。
这导致我遇到两个问题。首先是它似乎效率不高,但是我不确定我可以使用哪种算法。
第二个更多的是关于每次分配发生时都必须显式调用“ 2”的实用性,并且只有在分配被冲刷到会话中并具有正确的连接之后才必须显式调用“ 2”。如果我想查看ORM中反映的更改,则必须再次刷新会话。我认为,如果我可以按照orm表示重新计算祖先操作,那会容易得多。
解决方法
好吧,我去解决了自己的问题的解决方案。粗略的部分是将Floyd-Warshall算法应用于父节点的后代与子节点的后代的交集,但仅将输出应用于父级的并集。祖先和孩子的后代。我花了很多时间,最终在博客上发布了该过程,但这是代码。
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
association_table = Table(\'edges\',Base.metadata,Column(\'predecessor\',Integer,ForeignKey(\'nodes.id\'),primary_key=True),Column(\'successor\',primary_key=True))
path_table = Table(\'paths\',primary_key=True))
class Node(Base):
__tablename__ = \'nodes\'
id = Column(Integer,primary_key=True)
# extra columns
def __repr__(self):
return \'<Node #%r>\' % (self.id,)
successors = relationship(\'Node\',backref=\'predecessors\',secondary=association_table,primaryjoin=id == association_table.c.predecessor,secondaryjoin=id == association_table.c.successor)
before = relationship(\'Node\',backref=\'after\',secondary=path_table,primaryjoin=id == path_table.c.predecessor,secondaryjoin=id == path_table.c.successor)
def __lt__(self,other):
return other in self.before
def add_successor(self,other):
if other in self.successors:
return
self.successors.append(other)
self.before.append(other)
for descendent in other.before:
if descendent not in self.before:
self.before.append(descendent)
for ancestor in self.after:
if ancestor not in other.after:
other.after.append(ancestor)
def del_successor(self,other):
if not self < other:
# nodes are not connected,do nothing!
return
if not other in self.successors:
# nodes aren\'t adjacent,but this *could*
# be a warning...
return
self.successors.remove(other)
# we buld up a set of nodes that will be affected by the removal
# we just did.
ancestors = set(other.after)
descendents = set(self.before)
# we also need to build up a list of nodes that will determine
# where the paths may be. basically,we\'re looking for every
# node that is both before some node in the descendents and
# ALSO after the ancestors. Such nodes might not be comparable
# to self or other,but may still be part of a path between
# the nodes in ancestors and the nodes in descendents.
ancestors_descendents = set()
for ancestor in ancestors:
ancestors_descendents.add(ancestor)
for descendent in ancestor.before:
ancestors_descendents.add(descendent)
descendents_ancestors = set()
for descendent in descendents:
descendents_ancestors.add(descendent)
for ancestor in descendent.after:
descendents_ancestors.add(ancestor)
search_set = ancestors_descendents & descendents_ancestors
known_good = set() # This is the \'paths\' from the
# original algorithm.
# as before,we need to initialize it with the paths we
# know are good. this is just the successor edges in
# the search set.
for predecessor in search_set:
for successor in search_set:
if successor in predecessor.successors:
known_good.add((predecessor,successor))
# We now can work our way through floyd_warshall to resolve
# all adjacencies:
for ancestor in ancestors:
for descendent in descendents:
if (ancestor,descendent) in known_good:
# already got this one,so we don\'t need to look for an
# intermediate.
continue
for intermediate in search_set:
if (ancestor,intermediate) in known_good \\
and (intermediate,descendent) in known_good:
known_good.add((ancestor,descendent))
break # don\'t need to look any further for an
# intermediate,we can move on to the next
# descendent.
# sift through the bad nodes and update the links
for ancestor in ancestors:
for descendent in descendents:
if descendent in ancestor.before \\
and (ancestor,descendent) not in known_good:
ancestor.before.remove(descendent)
,插入时更新闭包,并根据orm进行更新:
def add_assignment(parent,child):
\"\"\"And parent-child relationship between two nodes\"\"\"
parent.descendants += child.descendants + [child]
child.ancestors += parent.ancestors + [parent]
parent.children += child
如果您需要删除分配,则在纯sql中这会更快:
def del_assignment(parent,child):
parent.children.remove(child)
head = [parent.id] + [node.id for node in parent.ancestors]
tail = [child.id] + [node.id for node in child.descendants]
session.flush()
session.execute(closure.delete(),and_(
closure.c.ancestor.in_(head),closure.c.descendant.in_(tail)))
session.expire_all()