如何使用字符串表示圖譜關系?


  知識圖譜聽起來很高大上,而且也應用廣泛,如用於血緣關系查詢,知識鏈展示,異動分析。而圖數據庫,你可以到網上搜搜,基本就是像 neo4j, janusgraph, HugeGraph,還有像阿里閉源提供的graphcompute服務...
  如果有個圖譜類似的需求,你會怎么辦呢?一來就上真的圖譜真的好嗎?也許前期就三兩個關系鏈,也許只是業務試水,你就去搞個真的圖數據庫過來?是不是太浪費了,因為業務如果發現不成可能立即改變方向了,而你卻為前期工作和設備買單。
  所以,實際上前期我們最好自己實現一些簡單的關系鏈維護即可。而其中的查詢實現,則只需要自己根據關系,做每個原子查詢實現即可。
  那么,為了能夠適應稍微的關系變化,也許我們還是需要效仿下圖數據庫的概念。那么,現在的第一個問題就是:如何使用文字表述一個圖關系鏈?

 

1. 如何定義規范?

  圖數據庫三大要素: 實體, 關系, 客體 。
  實際上要解決這個問題倒也不難,只要自己定一種表示方法,自己能看懂就行,不去管其他人。比如用 '1,2,3' 代表先1后2再3... 但實際上,想要表示稍微復雜點的結構,也許並不是特別容易呢。而且,如果想要考慮后續可能的切真正的圖數據庫,為何不參考下別人的標准呢?雖然沒有統一的標准,但無疑任何一家標准都會比自行定義要好要完善要考慮得更多。
  比如現在通用些的,cypher, gremlin... 可以說都是一些規范性質的東西,但目前圖數據庫查詢方面並沒有類似於sql92一樣的標准,將大家約束到一條統一戰線上。所以,我們如果也只能選擇一家站隊,大家可以網上搜索下資料,參考下來,好像cypher更形象化些,尤其是各種箭頭的使用比較方便。而gremlin則是使用一系列的單詞,通過鏈式編程的方式進行展示,無法評論好壞,這只能交給時間。標准化真的是一個好東西!
  cypher中比如要表示A與的B的關系可以是: (:A)-[:關系]->(:B)
  而對於多個復雜關系,則可以用多個類似的關系關聯起來就可以了。
  嗯,看起來不錯。表示的方式定好了,那么我們如何具體處理關系呢?

 

2. 如何表示一個現實的圖關系?

  如下圖所示,我們有如下關系,應該如何定義字符表達方法,以達到配置的目的?(如果能夠處理好如下圖關系,更復雜的關系又何嘗不是手到擒來?)

 

  按照第1節中我們定義的規范,我們可以用如下字符串表示。

    (:PEOPLE)-[:養寵物]->(:CAT)-[:吃]->(:RICE)
    ,(:PEOPLE)-[:吃]->(:RICE)
    ,(:PEOPLE)-[:養寵物]->(:DOG)
    ,(:PEOPLE)-[:擁有]->(:HOUSE)
    ,(:PEOPLE)-[:干活]->(:JOB)
    ,(:CAT)-[:朋友]->(:DOG)
    ,(:DOG)-[:吃]->(:RICE)
    ,(:JOB)-[:產出]->(:BRICK)
    ,(:HOUSE)<-[:構件]-(:BRICK)
    ,(:HOUSE)<-[:構件]-(:GLASS)

  應該說還是比較直觀的,基本上我們只要按照圖所示的關系,描述出出入邊和關系就可以了。而且還有相應的cypher官方規范支持,也不用寫文檔,大家就可以很方便的接受了。

 

3. 如何解析圖關系?

  如上,我們已經用字符串表示出了關系了。但單是字符串,是並不能被應用理解的。我們需要解析為具體的數據結構,然后才可以根據關系推導出具體的血緣依賴。這是本文的重點。

  實際也不復雜,我們僅僅使用到了cypher中非常少的幾個元素表示法,所以也僅需解析出該幾個字符,然后在內存中構建出相應的關系即可。

  具體代碼實現如下:

 

3.1. 解析框架

  所謂框架就是整體流程管控代碼,它會讓你明白整個系統是如何work的。

import com.my.mvc.app.common.helper.graph.GraphNodeEntityTree;
import com.my.mvc.app.common.helper.graph.NodeDiscoveryDirection;
import com.my.mvc.app.common.helper.graph.VertexEdgeSchemaDescriptor;
import com.my.mvc.app.common.helper.graph.VertexOrEdgeType;
import com.my.mvc.app.common.util.CommonUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 功能描述: 簡單圖語法解析器(類 cypher 語法)
 *
 *      請參考網上 cypher 資料
 *
 */
public class SimpleGraphSchemaSyntaxParser {

    /**
     * 解析配置圖譜關系配置為樹結構
     *
     * @param cypherGraphSchema 類cypher語法的 關系表示語句
     * @return 解析好的樹結構
     */
    public static GraphNodeEntityTree parseGraphSchemaAsTree(String cypherGraphSchema) {
        List<VertexEdgeSchemaDescriptor> flatNodeList = tokenize(cypherGraphSchema);
        return buildGraphAstTree(flatNodeList);
    }

    /**
     * 構建圖關系抽象語法樹
     *
     * @param flatNodeList 平展的圖節點列表
     * @return 構建好的實例
     */
    private static GraphNodeEntityTree buildGraphAstTree(
                        List<VertexEdgeSchemaDescriptor> flatNodeList) {
        Map<String, GraphNodeEntityTree>
                uniqVertexContainer = new HashMap<>();
        GraphNodeEntityTree root = new GraphNodeEntityTree(flatNodeList.get(0));
        uniqVertexContainer.put(flatNodeList.get(0).getVertexLabelType(), root);
        GraphNodeEntityTree parent;
        GraphNodeEntityTree afterNode;
        for ( int i = 1; i < flatNodeList.size(); i++ ) {
            VertexEdgeSchemaDescriptor vertexOrEdge1 = flatNodeList.get(i);
            if(vertexOrEdge1.getNodeType() == VertexOrEdgeType.EDGE) {
                // 整體關系構建以邊為依據,關聯前后兩個點,todo: 每個頂點都應該被添加到頂點列表中。以便后續可查
                VertexEdgeSchemaDescriptor vertexPrev = flatNodeList.get(i - 1);
                if(vertexPrev.getNodeType() != VertexOrEdgeType.VERTEX) {
                    continue;
                }
                if(++i >= flatNodeList.size()) {
                    throw new RuntimeException("圖譜語法錯誤:缺少客體關系配置, near 邊["
                            + vertexOrEdge1.getRawWord() + "]");
                }
                VertexEdgeSchemaDescriptor relation = vertexOrEdge1;
                VertexEdgeSchemaDescriptor vertexAfter = flatNodeList.get(i);
                parent = uniqVertexContainer.get(vertexPrev.getVertexLabelType());
                afterNode = uniqVertexContainer.get(vertexAfter.getVertexLabelType());
                if(parent == null) {
                    parent = root;
                    uniqVertexContainer.putIfAbsent(vertexPrev.getVertexLabelType(),
                            parent);
                }
                if(afterNode == null) {
                    afterNode = new GraphNodeEntityTree(vertexAfter);
                    uniqVertexContainer.put(vertexAfter.getVertexLabelType(), afterNode);
                }
                if(relation.getDirection() == NodeDiscoveryDirection.OUT) {
                    parent.addOutVertex(afterNode, relation);
                }
                else {
                    parent.addInVertex(afterNode, relation);
                }
            } // todo: 請嘗試添加頂點到uniqContainer中
if(vertexOrEdge1.getNodeType() == VertexOrEdgeType.VERTEX) {
uniqVertexContainer.putIfAbsent(new GraphNodeEntityTree(vertexOrEdge1));
} } root.setUniqVertexTypes(uniqVertexContainer); return root; }
/** * 拆分圖關系schema為 可理解的邊和點 * * @param cypherGraphSchema 建關系語句,如 (:A)-[:被引用]->(:B) * @return 拆解后的token列表 */ private static List<VertexEdgeSchemaDescriptor> tokenize(String cypherGraphSchema) { String[] relationArr = cypherGraphSchema.split(","); List<VertexEdgeSchemaDescriptor> flatNodeList = new ArrayList<>(); for (String relation1 : relationArr) { char[] src = relation1.trim().toCharArray(); for (int i = 0; i < src.length; i++) { char ch = src[i]; // 頂點 if(ch == '(') { StringBuilder specNameBuilder = new StringBuilder(); while (i + 1 < src.length) { char nextCh = src[i + 1]; if(nextCh == ':') { String vertexLabel = CommonUtil.readSplitWord( src, i, ':', ')', false); flatNodeList.add(VertexEdgeSchemaDescriptor.newVertex( specNameBuilder.toString() + ":" + vertexLabel, vertexLabel)); i += vertexLabel.length() + 2; break; } specNameBuilder.append(nextCh); ++i; } continue; } // 出射邊關系, (:SRC)-[:RELATION]->(:DST) if(ch == '-' && i + 1 < src.length && src[i + 1] == '[') { ++i; StringBuilder specNameBuilder = new StringBuilder(); while (i + 1 < src.length) { char nextCh = src[i + 1]; if(nextCh == ':') { String edgeLabel = CommonUtil.readSplitWord( src, i, ':', ']', false); int nextVertexStart = i + edgeLabel.length() + 2; if(nextVertexStart + 2 >= src.length) { throw new RuntimeException("圖譜語法錯誤: 缺少客體" + ", near '" + new String(src, nextVertexStart, src.length - nextVertexStart)); } if(src[++nextVertexStart] != '-' || src[++nextVertexStart] != '>') { throw new RuntimeException("圖譜語法錯誤: 主體后面需緊跟關系 ->" + ", near '" + new String(src, nextVertexStart, src.length - nextVertexStart)); } flatNodeList.add(VertexEdgeSchemaDescriptor.newEdge( specNameBuilder.toString() + ":" + edgeLabel, edgeLabel, NodeDiscoveryDirection.OUT)); i = nextVertexStart; break; } specNameBuilder.append(nextCh); ++i; } continue; } // 入射邊關系, (:SRC)<-[:RELATION]-(:DST) if(ch == '<') { if(i + 2 > src.length) { throw new RuntimeException("圖譜語法錯誤: 長度不匹配, near '" + new String(src, i, src.length - i)); } if(src[++i] != '-' || src[++i] != '[') { throw new RuntimeException("圖譜語法錯誤: 邊關系配置錯誤, near '" + new String(src, i, src.length - i)); } StringBuilder specNameBuilder = new StringBuilder(); while (i + 1 < src.length) { char nextCh = src[i + 1]; if(nextCh == ':') { String edgeLabel = CommonUtil.readSplitWord( src, i, ':', ']', false); int nextVertexStart = i + edgeLabel.length() + 2; if(nextVertexStart + 2 >= src.length) { throw new RuntimeException("圖譜語法錯誤: 缺少客體" + ", near '" + new String(src, nextVertexStart, src.length - nextVertexStart)); } if(src[++nextVertexStart] != '-' || src[nextVertexStart + 1] != '(') { throw new RuntimeException("圖譜語法錯誤: 主體后面需緊跟關系 -> " + ", near '" + new String(src, nextVertexStart, src.length - nextVertexStart)); } flatNodeList.add(VertexEdgeSchemaDescriptor.newEdge( specNameBuilder.toString() + ":" + edgeLabel, edgeLabel, NodeDiscoveryDirection.IN)); i = nextVertexStart; break; } specNameBuilder.append(nextCh); ++i; } } } } return flatNodeList; } }

  怎么樣,不復雜吧。就是兩個步驟:1. 解析每個單個元素信息; 2. 根據單元素信息,構建出上下級關系;

  使用 IN 代表入方向關系,用 OUT 代表出方向關系,每兩個頂點之間都有一條邊相連。大體就是這樣了。但是明顯,還有許多細節需要我們去考慮,比如邊關系放在哪里?如何添加相關節點?這些東西是需要特定的數據結構支持的。看我細細道來:

 

3.2. 單節點數結構

  所謂單節點,即是一個頂點的描述。當我們站在任意關系點上來看整體圖的結構,如果整個圖是連通的,那么理論上,通過這個節點可以探索到任意其他節點。所以,其實它的定義非常重要。

 

package com.my.mvc.app.common.helper.graph;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 功能描述: 簡單圖結構樹描述類
 *
 */
public class GraphNodeEntityTree {
    /**
     * 當前頂點描述
     */
    private VertexEdgeSchemaDescriptor vertex;

    /**
     * 關系邊容器
     */
    private Map<NodeDiscoveryDirection, List<RelationWithVertexDescriptor>>
                    relations = new HashMap<>();
    /**
     * 入射方向節點
     */
    private List<GraphNodeEntityTree> in = new ArrayList<>();

    /**
     * 出射方向節點
     */
    private List<GraphNodeEntityTree> out = new ArrayList<>();

    /**
     * 所有頂點實例容器
     */
    private Map<String, GraphNodeEntityTree> uniqVertexTypes;
    
    public GraphNodeEntityTree(VertexEdgeSchemaDescriptor vertex) {
        this.vertex = vertex;
        uniqVertexTypes = new HashMap<>();
    }

    public void setUniqVertexTypes(Map<String, GraphNodeEntityTree> uniqVertexTypes) {
        this.uniqVertexTypes = uniqVertexTypes;
    }

    public void addRelation(VertexEdgeSchemaDescriptor srcEntity,
                            VertexEdgeSchemaDescriptor relation,
                            VertexEdgeSchemaDescriptor dstEntity) {
        List<RelationWithVertexDescriptor> list = relations.computeIfAbsent(
                                relation.getDirection(), k -> new ArrayList<>());
        list.add(new RelationWithVertexDescriptor(srcEntity, relation, dstEntity));
    }

    public GraphNodeEntityTree addInVertex(GraphNodeEntityTree embeddedEntity,
                                           VertexEdgeSchemaDescriptor relation) {
        embeddedEntity.addOutVertexInner(this, relation.reverseDirection());
        addInVertexInner(embeddedEntity, relation);
        return embeddedEntity;
    }

    private GraphNodeEntityTree addInVertexInner(GraphNodeEntityTree embeddedEntity,
                                                VertexEdgeSchemaDescriptor relation) {
        in.add(embeddedEntity);
        addRelation(vertex, relation, embeddedEntity.getVertex());
        return embeddedEntity;
    }

    public GraphNodeEntityTree addOutVertex(GraphNodeEntityTree embeddedEntity,
                                            VertexEdgeSchemaDescriptor relation) {
        embeddedEntity.addInVertexInner(this, relation.reverseDirection());
        addOutVertexInner(embeddedEntity, relation);
        return embeddedEntity;
    }

    private GraphNodeEntityTree addOutVertexInner(GraphNodeEntityTree embeddedEntity,
                                                  VertexEdgeSchemaDescriptor relation) {
        out.add(embeddedEntity);
        addRelation(this.getVertex(), relation, embeddedEntity.getVertex());
        return embeddedEntity;
    }

    public VertexEdgeSchemaDescriptor getVertex() {
        return vertex;
    }

    /**
     * 獲取關系名稱
     *
     * @param nodeIndex 節點序號
     * @param direction 方向
     * @return 關系名稱描述
     */
    public String getRelationName(int nodeIndex, NodeDiscoveryDirection direction) {
        List<RelationWithVertexDescriptor> list = relations.get(direction);
        if(list == null || list.isEmpty()) {
            return null;
        }
        return list.get(nodeIndex).getRelationName();
    }

    public List<GraphNodeEntityTree> getIn() {
        return in;
    }

    public List<GraphNodeEntityTree> getOut() {
        return out;
    }

    /**
     * 快速獲取圖節點根(根據頂點label)
     *
     * @param vertexLabel 頂點標識
     * @return 節點所在實例, 找不到對應節點則返回 null
     */
    public GraphNodeEntityTree getNodeEntityTreeByVertexLabel(
            String vertexLabel) {
        return uniqVertexTypes.get(vertexLabel);
    }
}

  可以說后續的操作入口都是在這里的,所以重點關注。

 

3.3. 圖頂點和邊描述類

  最開始有一個token化的過程,那么token化之后,如何定義也比較重要,我們統一使用一個描述類來定義:

package com.my.mvc.app.common.helper.graph;


/**
 * 功能描述: 圖頂點和邊描述類
 *
 */
public class VertexEdgeSchemaDescriptor {
    private String rawWord;
    private VertexOrEdgeType nodeType;
    private String vertexLabelType;
    private String relationName;
    private NodeDiscoveryDirection direction;

    private VertexEdgeSchemaDescriptor(String rawWord,
                                       VertexOrEdgeType nodeType,
                                       String vertexLabelType,
                                       String relationName,
                                       NodeDiscoveryDirection direction) {
        this.rawWord = rawWord;
        this.nodeType = nodeType;
        this.vertexLabelType = vertexLabelType;
        this.relationName = relationName;
        this.direction = direction;
    }

    /**
     * 新建頂點實例
     *
     * @param rawWord 原始字符描述
     * @param vertexLabelType 解析后的頂點類型(枚舉完成所有點類型)
     * @return 頂點實例
     */
    public static VertexEdgeSchemaDescriptor newVertex(String rawWord, String vertexLabelType) {
        return new VertexEdgeSchemaDescriptor(rawWord, VertexOrEdgeType.VERTEX,
                vertexLabelType, null, null);
    }

    /**
     * 新建邊實例
     *
     * @param rawWord 原始字符描述
     * @param relationName 關系名稱(當id使用)
     * @param direction 關系方向( -> 出方向OUT, <- 入方向IN )
     * @return 邊實例
     */
    public static VertexEdgeSchemaDescriptor newEdge(String rawWord,
                                                     String relationName,
                                                     NodeDiscoveryDirection direction) {
        return new VertexEdgeSchemaDescriptor(rawWord, VertexOrEdgeType.EDGE,
                null, relationName, direction);
    }

    public String getRawWord() {
        return rawWord;
    }

    public VertexOrEdgeType getNodeType() {
        return nodeType;
    }

    public String getVertexLabelType() {
        return vertexLabelType;
    }

    public String getRelationName() {
        return relationName;
    }

    public NodeDiscoveryDirection getDirection() {
        return direction;
    }

    public VertexEdgeSchemaDescriptor reverseDirection() {
        return new VertexEdgeSchemaDescriptor(rawWord, nodeType,
                vertexLabelType, "-" + relationName,
                direction.reverse());
    }

    @Override
    public String toString() {
        // 點描述
        if(nodeType == VertexOrEdgeType.VERTEX) {
            return nodeType + "{" +
                    "rawWord='" + rawWord + '\'' +
                    ", vertexLabelType=" + vertexLabelType +
                    '}';
        }
        // 邊描述
        return nodeType + "{" +
                "rawWord='" + rawWord + '\'' +
                ", relationName='" + relationName + '\'' +
                ", direction=" + direction +
                '}';
    }
}

  主要就是原始字符串,定義邊、定義點。類似與單詞的聚合吧。

 

3.4. 節點關系描述

  我們需要清楚地知道各個點與各個點間的關系,所以需要一個關系描述類,來展示這東西。(實際上核心並未使用該關系)

package com.my.mvc.app.common.helper.graph;

/**
 * 功能描述: 關系實例, 實體 -> 關系 -> 客體
 *
 */
public class RelationWithVertexDescriptor {
    /**
     * 源點、起點
     */
    private final VertexEdgeSchemaDescriptor srcVertex;

    /**
     * 目標點
     */
    private final VertexEdgeSchemaDescriptor dstVertex;

    /**
     * 關系(名稱)
     */
    private final VertexEdgeSchemaDescriptor relation;

    public RelationWithVertexDescriptor(VertexEdgeSchemaDescriptor srcVertex,
                                        VertexEdgeSchemaDescriptor relation,
                                        VertexEdgeSchemaDescriptor dstVertex) {
        this.srcVertex = srcVertex;
        this.dstVertex = dstVertex;
        this.relation = relation;
    }

    public VertexEdgeSchemaDescriptor getSrcVertex() {
        return srcVertex;
    }

    public VertexEdgeSchemaDescriptor getDstVertex() {
        return dstVertex;
    }

    /**
     * 獲取當前關系名稱
     */
    public String getRelationName() {
        return relation.getRelationName();
    }

    @Override
    public String toString() {
        if(relation.getDirection() == NodeDiscoveryDirection.OUT) {
            return srcVertex.getRawWord() + "(" + srcVertex.getVertexLabelType() + ")" +
                    " -> " + relation.getRelationName() +
                    " -> " + dstVertex.getRawWord() + "(" + dstVertex.getVertexLabelType() + ")"
                    ;
        }
        return srcVertex.getRawWord() + "(" + srcVertex.getVertexLabelType() + ")" +
                " <- " + relation.getRelationName() +
                " <- " + dstVertex.getRawWord() + "(" + dstVertex.getVertexLabelType() + ")"
                ;

    }
}

  雖實際用處不大,但是當你在debug的時候,這個描述類可以很方便地讓你觀察到解析是否正確。

 

3.5. 幾個基礎類型定義

  1. 方向定義(實際上方向我們一定要有的概念就是,它永遠是相對的,如 A->B,對A來說是出方向,而對B來說則是入方向,此概念不清將會帶來大麻煩)

package com.my.mvc.app.common.helper.graph;

/**
 * 功能描述: 探索方向定義
 *
 **/
public enum NodeDiscoveryDirection {

    /**
     * 入方向, 上游
     */
    IN,

    /**
     * 出方向, 下游
     */
    OUT,
    ;

    public NodeDiscoveryDirection reverse() {
        if(this == OUT) {
            return IN;
        }
        return OUT;
    }
}

  2. 邊或點類型定義 

package com.my.mvc.app.common.helper.graph;

/**
 * 功能描述: 邊或點類型定義
 *
 */
public enum VertexOrEdgeType {
    VERTEX,
    EDGE,
    ;
}

  此類型雖然簡單,但卻會影響到后續整體抽象語法樹的構建。如此,整個解析模塊就完成了。你可以完整的將如上字符解析為實體關系了。

 

4. 單元測試

  經過測試才算真正可用。

package com.my.test.common.parser;

import com.my.mvc.app.common.helper.SimpleGraphSchemaSyntaxParser;
import com.my.mvc.app.common.helper.graph.GraphNodeEntityTree;
import com.my.mvc.app.common.helper.graph.NodeDiscoveryDirection;
import org.junit.Test;

import java.util.List;

public class SimpleGraphSchemaSyntaxParserTest {

    // 測試腳本
    @Test
    public void testParseGraphSchema() throws InterruptedException {
        String graphSchema = "(:PEOPLE)-[:養寵物]->(:CAT)-[:吃]->(:RICE)\n"
                + ",(:PEOPLE)-[:吃]->(:RICE)\n"
                + ",(:PEOPLE)-[:養寵物]->(:DOG)\n"
                + ",(:PEOPLE)-[:擁有]->(:HOUSE)"
                + ",(:PEOPLE)-[:干活]->(:JOB)"
                + ",(:CAT)-[:朋友]->(:DOG)"
                + ",(:DOG)-[:吃]->(:RICE)"
                + ",(:JOB)-[:產出]->(:BRICK)"
                + ",(:HOUSE)<-[:構件]-(:BRICK)"
                + ",(:HOUSE)<-[:構件]-(:GLASS)"
                ;
        GraphNodeEntityTree tree = SimpleGraphSchemaSyntaxParser
                .parseGraphSchemaAsTree(graphSchema);
        String searchFromLabel = "PEOPLE";
        NodeDiscoveryDirection direction = NodeDiscoveryDirection.OUT;
        int maxDepth = 10;
        System.out.println("->" + searchFromLabel + ", direction:" + direction + ", depth:" + maxDepth);
        GraphNodeEntityTree searchRootFrom
                = tree.getNodeEntityTreeByVertexLabel(searchFromLabel);
        int allNodes = traversalNodesWithDirection(searchRootFrom,
                direction, maxDepth, maxDepth);
        System.out.println("allNodes: " + allNodes);
        Thread.sleep(5);
    }

    /**
     * 按某方向遍歷所有節點
     *
     * @param root 搜索起點
     * @param direction 方向, IN, OUT
     * @param maxDepth 搜索最大深度
     * @param remainSearchDepth 剩余搜索深度
     * @return 所有節點數
     */
    private static int traversalNodesWithDirection(GraphNodeEntityTree root,
                                                   NodeDiscoveryDirection direction,
                                                   int maxDepth,
                                                   int remainSearchDepth) {
        if(remainSearchDepth <= 0) {
            return 0;
        }
        List<GraphNodeEntityTree> subBranches;
        if(direction == NodeDiscoveryDirection.OUT) {
            subBranches = root.getOut();
        }
        else {
            subBranches = root.getIn();
        }
        if(subBranches == null || subBranches.isEmpty()) {
            return 0;
        }
        String whitespaceUnit = "    ";
        StringBuilder preWhitespaceBuilder = new StringBuilder(whitespaceUnit);
        for (int i = 1; i < maxDepth - remainSearchDepth + 1; i++) {
            preWhitespaceBuilder.append(whitespaceUnit);
        }
        int allNodes = 0;
        String preWhitespace = preWhitespaceBuilder.toString();
        for (int i = 0; i < subBranches.size(); i++) {
            GraphNodeEntityTree br1 = subBranches.get(i);
            String relationName = root.getRelationName(i, direction);
            allNodes++;
            System.out.println(preWhitespace + "->" +
                    relationName + "->" + br1.getVertex().getRawWord());
            allNodes += traversalNodesWithDirection(br1, direction,
                    maxDepth, remainSearchDepth - 1);
        }
        return allNodes;
    }

}

  結果樣例如下:

->PEOPLE, direction:OUT, depth:10
    ->養寵物->:CAT
        ->吃->:RICE
        ->朋友->:DOG
            ->吃->:RICE
    ->吃->:RICE
    ->養寵物->:DOG
        ->吃->:RICE
    ->擁有->:HOUSE
    ->干活->:JOB
        ->產出->:BRICK
            ->-構件->:HOUSE

      看起來不錯,已經能夠展示出關系鏈了。當有多個入口時,使用','分隔另起一個描述即可。如果我們不考慮另一個復雜問題的話:環形關系問題。以上就足夠應付業務了。

       ok,有了以上關系鏈的定義,我們可以很輕松地遍歷出血緣關系了。而要實現真正地血緣分析,則肯定是數據驅動的,所以,你需要為每個關系定義一些原子查詢,這個查詢可以基於關系型數據庫實現,也可以基於redis之類的nosql實現,也可以基於es之類的產品實現。總之,底層可以任意更換。從外面看來,這和一個圖數據庫沒啥兩樣,主體-關系-客體同樣都有體現。在數據不大的情況下,不失為一個好的方法。當業務方向確立,需要進行更多更復雜業務時,再切換為真正的圖數據庫,使用圖計算,也是方便的。這也是一個正常的技術發展路線,不是嗎。

        


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM