관리 메뉴

HAMA 블로그

Neo4j - Traversal 본문

NoSQL

Neo4j - Traversal

[하마] 이승현 (wowlsh93@gmail.com) 2015. 10. 30. 17:34

33.8. Traversal


The Matrix

이것은 우리가 순회할  첫번째 그래프 모습이다.


Figure 33.2. Matrix node space view

examples-matrix.png


친구들  / 친구들의 친구들


private Traverser getFriends(
        final Node person )
{
    TraversalDescription td = graphDb.traversalDescription()
            .breadthFirst()
            .relationships( RelTypes.KNOWS, Direction.OUTGOING )
            .evaluator( Evaluators.excludeStartPosition() );
    return td.traverse( person );
}


 실제 순회를 돌아보고 결과를 찍어보자.

int numberOfFriends = 0;
String output = neoNode.getProperty( "name" ) + "'s friends:\n";
Traverser friendsTraverser = getFriends( neoNode );
for ( Path friendPath : friendsTraverser )
{
    output += "At depth " + friendPath.length() + " => "
              + friendPath.endNode()
                      .getProperty( "name" ) + "\n";
    numberOfFriends++;
}
output += "Number of friends found: " + numberOfFriends + "\n";


결과:

Thomas Anderson's friends:
At depth 1 => Morpheus
At depth 1 => Trinity
At depth 2 => Cypher
At depth 3 => Agent Smith
Number of friends found: 4


누가 매트릭스를 코드했나?


private Traverser findHackers( final Node startNode )
{
    TraversalDescription td = graphDb.traversalDescription()
            .breadthFirst()
            .relationships( RelTypes.CODED_BY, Direction.OUTGOING )
            .relationships( RelTypes.KNOWS, Direction.OUTGOING )
            .evaluator(
                    Evaluators.includeWhereLastRelationshipTypeIs( RelTypes.CODED_BY ) );
    return td.traverse( startNode );
}


:결과 

String output = "Hackers:\n";
int numberOfHackers = 0;
Traverser traverser = findHackers( getNeoNode() );
for ( Path hackerPath : traverser )
{
    output += "At depth " + hackerPath.length() + " => "
              + hackerPath.endNode()
                      .getProperty( "name" ) + "\n";
    numberOfHackers++;
}
output += "Number of hackers found: " + numberOfHackers + "\n";


이제 우리는 알게됬다.

Hackers:
At depth 4 => The Architect
Number of hackers found: 1



순서에 따라서  경로를 걸어보자


토이그래프를 만들자

Node A = db.createNode();
Node B = db.createNode();
Node C = db.createNode();
Node D = db.createNode();

A.createRelationshipTo( C, REL2 );
C.createRelationshipTo( D, REL3 );
A.createRelationshipTo( B, REL1 );
B.createRelationshipTo( C, REL2 );

example-ordered-path.svg


Now, the order of relationships (REL1 → REL2 → REL3) is stored in an ArrayList. Upon traversal, the Evaluator can check against it to ensure that only paths are included and returned that have the predefined order of relationships:


어떻게 경로를 걸어갈지 정의하자.

final ArrayList<RelationshipType> orderedPathContext = new ArrayList<RelationshipType>();
orderedPathContext.add( REL1 );
orderedPathContext.add( withName( "REL2" ) );
orderedPathContext.add( withName( "REL3" ) );
TraversalDescription td = db.traversalDescription()
        .evaluator( new Evaluator()
        {
            @Override
            public Evaluation evaluate( final Path path )
            {
                if ( path.length() == 0 )
                {
                    return Evaluation.EXCLUDE_AND_CONTINUE;
                }
                RelationshipType expectedType = orderedPathContext.get( path.length() - 1 );
                boolean isExpectedType = path.lastRelationship()
                        .isType( expectedType );
                boolean included = path.length() == orderedPathContext.size() && isExpectedType;
                boolean continued = path.length() < orderedPathContext.size() && isExpectedType;
                return Evaluation.of( included, continued );
            }
        } )
        .uniqueness( Uniqueness.NODE_PATH );

Note that we set the uniqueness to Uniqueness.NODE_PATH as we want to be able to revisit the same node dureing the traversal, but not the same path.


순회를 하고 결과를 찍어보자

Traverser traverser = td.traverse( A );
PathPrinter pathPrinter = new PathPrinter( "name" );
for ( Path path : traverser )
{
    output += Paths.pathToString( path, pathPrinter );
}


결과:

(A)--[REL1]-->(B)--[REL2]-->(C)--[REL3]-->(D)


여기서 우리는 패스 아웃풋을 표현하기위해 커스텀 클래스를 사용했다. 

static class PathPrinter implements Paths.PathDescriptor<Path>
{
    private final String nodePropertyKey;

    public PathPrinter( String nodePropertyKey )
    {
        this.nodePropertyKey = nodePropertyKey;
    }

    @Override
    public String nodeRepresentation( Path path, Node node )
    {
        return "(" + node.getProperty( nodePropertyKey, "" ) + ")";
    }

    @Override
    public String relationshipRepresentation( Path path, Node from, Relationship relationship )
    {
        String prefix = "--", suffix = "--";
        if ( from.equals( relationship.getEndNode() ) )
        {
            prefix = "<--";
        }
        else
        {
            suffix = "-->";
        }
        return prefix + "[" + relationship.getType().name() + "]" + suffix;
    }
}


Uniqueness of Paths in traversals


This example is demonstrating the use of node uniqueness. Below an imaginary domain graph with Principals that own pets that are descendant to other pets.

Figure 33.3. Descendants Example Graph

Descendants-Example-Graph-Uniqueness-of-Paths-in-traversals.svg

In order to return all descendants of Pet0 which have the relation owns to Principal1 (Pet1 and Pet3), the Uniqueness of the traversal needs to be set to NODE_PATH rather than the default NODE_GLOBAL so that nodes can be traversed more that once, and paths that have different nodes but can have some nodes in common (like the start and end node) can be returned.

final Node target = data.get().get( "Principal1" );
TraversalDescription td = db.traversalDescription()
        .uniqueness( Uniqueness.NODE_PATH )
        .evaluator( new Evaluator()
{
    @Override
    public Evaluation evaluate( Path path )
    {
        boolean endNodeIsTarget = path.endNode().equals( target );
        return Evaluation.of( endNodeIsTarget, !endNodeIsTarget );
    }
} );

Traverser results = td.traverse( start );

This will return the following paths:

(2)--[descendant,2]-->(3)<--[owns,5]--(4)
(2)--[descendant,0]-->(0)<--[owns,3]--(4)

In the default path.toString() implementation, (1)--[knows,2]-->(4) denotes a node with ID=1 having a relationship with ID 2 or type knows to a node with ID-4.

Let’s create a new TraversalDescription from the old one, having NODE_GLOBAL uniqueness to see the difference.

[Tip]Tip

The TraversalDescription object is immutable, so we have to use the new instance returned with the new uniqueness setting.

TraversalDescription nodeGlobalTd = td.uniqueness( Uniqueness.NODE_GLOBAL );
results = nodeGlobalTd.traverse( start );

Now only one path is returned:

(2)--[descendant,2]-->(3)<--[owns,5]--(4)


Social network

Social networks (know as social graphs out on the web) are natural to model with a graph. This example shows a very simple social model that connects friends and keeps track of status updates.


Simple social model

Figure 33.4. Social network data model

socnet-model.svg

The data model for a social network is pretty simple: Persons with names and StatusUpdates with timestamped text. These entities are then connected by specific relationships.

  • Person

    • friend: relates two distinct Person instances (no self-reference)
    • status: connects to the most recent StatusUpdate
  • StatusUpdate

    • next: points to the next StatusUpdate in the chain, which was posted before the current one

Status graph instance

The StatusUpdate list for a Person is a linked list. The head of the list (the most recent status) is found by following status. Each subsequent StatusUpdate is connected by next.

Here’s an example where Andreas Kollegger micro-blogged his way to work in the morning:

andreas-status-updates.svg

To read the status updates, we can create a traversal, like so:

TraversalDescription traversal = graphDb().traversalDescription()
        .depthFirst()
        .relationships( NEXT );

This gives us a traverser that will start at one StatusUpdate, and will follow the chain of updates until they run out. Traversers are lazy loading, so it’s performant even when dealing with thousands of statuses — they are not loaded until we actually consume them.

Activity stream

Once we have friends, and they have status messages, we might want to read our friends status' messages, in reverse time order — latest first. To do this, we go through these steps:

  1. Gather all friend’s status update iterators in a list — latest date first.
  2. Sort the list.
  3. Return the first item in the list.
  4. If the first iterator is exhausted, remove it from the list. Otherwise, get the next item in that iterator.
  5. Go to step 2 until there are no iterators left in the list.

Animated, the sequence looks like this.

The code looks like:

PositionedIterator<StatusUpdate> first = statuses.get(0);
StatusUpdate returnVal = first.current();

if ( !first.hasNext() )
{
    statuses.remove( 0 );
}
else
{
    first.next();
    sort();
}

return returnVal;




http://neo4j.com/docs/stable/tutorials-java-embedded-traversal.html

'NoSQL' 카테고리의 다른 글

Neo4j - 인덱스 사용하기  (0) 2015.10.30
Neo4j 인사이드 : 파일 스토리지  (0) 2015.10.30
시계열 DB (OpenTSDB , 인플럭스 DB , Graphite ) 정리  (0) 2015.10.22
MongoDB vs Couchbase (2)  (0) 2015.09.03
MongoDB vs Couchbase (1)  (0) 2015.09.03
Comments