GraphGists

网络版本

最近,Ian Robinson发表了一篇关于网络版本控制的精彩博客(在此阅读),内容涉及使用身份节点以及将结构与状态分离。在这个Gist中,我想通过介绍另一种使用relationnodes的方法来发表我的看法。这更多是关于网络结构,而非节点状态,但它以不同的方式处理版本控制。正如Ian在他的博客中所说,版本控制总是需要权衡取舍,因为它会增加存储和处理的额外负担。

Relationnodes?

Relationnodes是什么?

关系节点(Relation nodes)是位于两个网络节点之间的中间节点,表示节点之间的关系。

因此,如果A和B是两个网络节点,与其用关系[:RELTYPE]连接它们,不如在它们之间插入一个关系节点(relationnode),并用[:RS][RE]连接,它们分别代表关系开始(relation start)关系结束(relation end)

Gist01

为什么使用relationnodes?

Neo4j(我偏好的图数据库)当前2.0.3版本不允许在关系和节点之间创建关系。如果我想表示特定网络节点之间的特定关系属于特定网络(该网络本身由一个节点表示),我就需要这样做。

此外,我的用例要求我在不同网络之间共享网络节点,但以不同的方式组合。

另一个原因是网络由节点之间的关系决定,而非节点本身。如果我有两个节点A和B,这并不能告诉我它们的关系。无论是方向、类型,还是连接它们的多个关系,都无法得知。另一方面,如果我知道一个关系,我就知道它的起始/结束节点及其类型。节点可以独立存在,而关系只能在有起始和结束节点的情况下存在。

使用这种方法的第四个原因是它允许我比较不同的网络。例如,回答“网络1的第3版可以与网络2的第1版共存吗?”这样的问题。然而,这不会是本次Gist的主题,因为它需要额外的思考时间。

创建网络

设置

创建网络的过程包括以下步骤

  • 创建表示网络的节点

  • 创建网络节点

  • 创建通过[:RS][:RE]关系连接到网络节点,并通过[:UPDATE {type:'ADD', version:1}]连接到网络节点的关系节点(relationnodes)。

通过设置查询创建了网络1的第一个版本,将A连接到B再连接到C。请注意,[:UPDATE]关系的version属性可以简单地替换为或附带一个时间戳属性,使图具有时间感知能力。

CREATE (_0:`nw` {`name`:"Network1"})
CREATE (_5:`nwnode` {`name`:"A"})
CREATE (_6:`nwnode` {`name`:"B"})
CREATE (_7:`nwnode` {`name`:"C"})
CREATE (_13:`relationnode` {`id`:1})
CREATE (_14:`relationnode` {`id`:2})
CREATE _5-[:`RS`]->_13
CREATE _6-[:`RS`]->_14
CREATE _13-[:`RE`]->_6
CREATE _14-[:`RE`]->_7
CREATE _0-[:`UPDATE` {`type`:"ADD", `version`:1}]->_14
CREATE _0-[:`UPDATE` {`type`:"ADD", `version`:1}]->_13

此网络中的关系是

MATCH (s:nwnode)-[:RS]->(rn:relationnode)-[:RE]->(e:nwnode)
RETURN s.name as From,e.name as To ORDER BY From,To

向网络添加节点

如果需要向网络添加一个节点,我们需要

  • 创建新节点

  • 创建一个关系节点(relationnode)

  • 这次创建一个[:UPDATE]关系,版本为2

MATCH (nw:nw {name:"Network1"}),(sn:nwnode {name:"C"})
CREATE (_8:`nwnode` {`name`:"D"})
CREATE (_15:`relationnode` {`id`:3})
CREATE (sn)-[:`RS`]->_15-[:`RE`]->_8
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->_15

网络版本2中的关系是

MATCH (s:nwnode)-[:RS]->(rn:relationnode)-[:RE]->(e:nwnode)
RETURN s.name as From,e.name as To ORDER BY From,To

从网络中移除节点

如果需要从网络中移除节点,我们所做的是向相关的关系节点(relationnodes)添加一个[:UPDATE {type:'REMOVE', version:3}]关系。因此,从网络1中完全移除节点“C”是一个涉及以下步骤的问题:

  • 找到将“C”与网络1连接的关系节点(relationnodes)

  • 在网络1和这些关系节点(relationnodes)之间创建[:UPDATE {type:'REMOVE', version:3}]关系

MATCH (nw:nw {name:"Network1"})-[:UPDATE]->(rn:relationnode)-[:RS|RE]-(n:nwnode {name:"C"})
WITH nw,COLLECT(DISTINCT rn) AS rns
FOREACH ( i IN rns |
  CREATE (nw)-[:`UPDATE` {`type`:"REMOVE", `version`:3}]->(i)
)

现在存在的完整关系列表由这个查询给出

MATCH (nw:nw {name:"Network1"})-[u:UPDATE]->(rn:relationnode),(s:nwnode)-[:RS]->(rn)-[:RE]->(e:nwnode)
RETURN u.version AS Version,u.type AS UpdateType,s.name AS From, e.name AS To
ORDER BY Version,UpdateType,From

为了获取网络版本3中的关系,我们必须将搜索范围限制在网络内,并且只考虑那些最近的[:UPDATE]类型为“ADD”的事务。

你会看到,通过移除C,我们也把D从网络中切断了。这很可惜,但幸运的是我们也可以在现有节点之间创建关系。

如果需要向网络添加一个节点,我们需要

  • 获取节点

  • 创建一个关系节点(relationnode)

  • 这次创建一个[:UPDATE]关系,版本为4

MATCH (nw:nw {name:"Network1"}),(sn:nwnode {name:"B"}),(en:nwnode {name:"D"})
CREATE (rn:`relationnode` {`id`:7})
CREATE (sn)-[:`RS`]->(rn)-[:`RE`]->(en)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:4}]->(rn)

网络的更新版本是

历史版本

只只需考虑直到该版本的[:UPDATE]关系,即可检索到网络的特定版本。

网络1的版本1

并以图的形式呈现(目前仅在此链接有效 14MAY2014 https://jexp.github.io/graphgist/?40364ac2a52f57aa520a 。感谢@Jim_Salmons指出)

网络1的版本2

网络1的版本3

添加第二个网络

采用这种方法的原因之一是,我需要在第二个网络中重用这些网络节点,且该网络有自己的关系。

让我们创建一个有两个版本的网络2。

  • 版本1

    • C到A

  • 版本2

    • A到D

    • B到A

    • D到E

    • E到A

    • E到B

MATCH (na:nwnode {name:"A"}),(nb:nwnode {name:"B"}),(nc:nwnode {name:"C"}),(nd:nwnode {name:"D"})
CREATE (nw:`nw` {`name`:"Network2"})
CREATE (ne:`nwnode` {`name`:"E"})

CREATE (nc)-[:`RS`]->(rn1:relationnode)-[:`RE`]->(na)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:1}]->(rn1)
CREATE (nw)-[:`UPDATE` {`type`:"REMOVE", `version`:2}]->(rn1)

CREATE (nb)-[:`RS`]->(rn2:relationnode)-[:`RE`]->(na)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn2)

CREATE (na)-[:`RS`]->(rn3:relationnode)-[:`RE`]->(nd)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn3)

CREATE (nd)-[:`RS`]->(rn4:relationnode)-[:`RE`]->(ne)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn4)

CREATE (ne)-[:`RS`]->(rn5:relationnode)-[:`RE`]->(nb)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn5)

CREATE (ne)-[:`RS`]->(rn6:relationnode)-[:`RE`]->(na)
CREATE (nw)-[:`UPDATE` {`type`:"ADD", `version`:2}]->(rn6)

之后,当前网络2的关系是

验证一下

当然,网络1的版本2仍然完整无损。

后记

这是我的第一个Gist。设置起来很有趣。要是能有可视化查询结果而非整个图的//graph之类的东西就好了。更新:我们已经在https://jexp.github.io/graphgist/?40364ac2a52f57aa520a上有了 :) 我想在不久的将来也会在https://gist.neo4j.org/上提供。

我倾向于尽可能地以图的方式来处理事物。不确定这次是否成功了,但我努力记住,打开和关闭节点与关系而非进行遍历会带来成本。

[:UPDATE]关系添加另一个属性{status: "pending"},将允许我运行Cypher查询来回答诸如“如果我这样做会怎样?”的问题。

正如我之前所说,向[:UPDATE]关系添加时间戳将使其具有时间感知能力。你也可以创建一个版本节点的链表,并将[:UPDATE]链接到这些节点。

比较不同的网络将是我下一个Gist的主题。

我还将探讨使用关系节点(relationnodes)时的可视化方面。有时你想看到它们,有时它们会碍事,因为力导向布局可能会在你不想要的地方产生角度。幸运的是,Cypher允许你以可视化引擎可以处理的方式调整结果,并“欺骗”可视化引擎,使其认为你提供给它的是一个关系。

感兴趣?在@tomzeppenfeldt / @ophileon关注我。任何评论当然也欢迎。

© . All rights reserved.