5

SQLAlchemy に関して、 Partially Ordered Setの数学的特性を持つ構造を実装しています。この構造では、エッジを一度に 1 つずつ追加および削除できる必要があります。

私の現在の最良の設計では、2 つの隣接リストを使用します。1 つは割り当てリスト (ハス ダイアグラムのほぼエッジ) です。これは、ノードのどのペアが順序付きとして明示的に設定されているかを保持する必要があり、もう 1 つの隣接リストは推移的であるためです。これにより、あるノードが別のノードに対して順序付けられているかどうかを効率的にクエリできます。現在、エッジが割り当て隣接リストに追加または削除されるたびに、推移閉包を再計算しています。

次のようになります。

assignment = Table('assignment', metadata,
    Column('parent', Integer, ForeignKey('node.id')),
    Column('child', Integer, ForeignKey('node.id')))

closure = Table('closure', metadata,
    Column('ancestor', Integer, ForeignKey('node.id')),
    Column('descendent', Integer, 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))

floyd_warshall()同じ名前によるアルゴリズムの実装です。

これは私に2つの問題を引き起こしています。1つ目は、あまり効率的ではないようですが、代わりにどのようなアルゴリズムを使用できるかわかりません。

Node.recompute_ancestry()2 つ目は、割り当てが発生するたびに、割り当てがセッションにフラッシュされ、適切な接続が確立されたにのみ、明示的に呼び出す必要があるという実用性に関するものです。ORM に反映された変更を確認したい場合は、もう一度セッションをフラッシュする必要があります。祖先の再計算操作を orm の観点から表現できれば、はるかに簡単になると思います。

4

2 に答える 2

1

さて、私は行って、自分の問題の解決策を考え出しました。その大まかな部分は、親ノードの祖先の子孫と子ノードの子孫の祖先の交点に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', Integer, 
           ForeignKey('nodes.id'), primary_key=True))

path_table = Table('paths', Base.metadata,
    Column('predecessor', Integer, 
           ForeignKey('nodes.id'), primary_key=True),
    Column('successor', Integer, 
           ForeignKey('nodes.id'), 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)
于 2011-05-30T20:48:26.157 に答える
0

挿入するときにクロージャを更新し、or に関してこれを行います。

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()
于 2011-05-26T08:59:42.177 に答える