Prev |  | Next

## 4.5. Traversal

For reading about traversals, see Chapter 6, The Traversal Framework.

For more examples of traversals, see Chapter 7, Data Modeling Examples.

### 4.5.1. The Matrix

The traversals from the Matrix example above, this time using the new traversal API:

| Tip
The source code of the examples is found here: NewMatrix.java

Friends and friends of friends.

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

Let’s perform the actual traversal and print the results:

    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";

Which will give us the following output:

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

Who coded the Matrix?

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

Print out the result:

    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";

Now we know who coded the Matrix:

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

#### Walking an ordered path

This example shows how to use a path context holding a representation of a path.

| Tip
The source code of this example is found here: OrderedPath.java

Create a toy graph.

    Node A = db.createNode();
    Node B = db.createNode();
    Node C = db.createNode();
    Node D = db.createNode();
    A.createRelationshipTo( B, REL1 );
    B.createRelationshipTo( C, REL2 );
    C.createRelationshipTo( D, REL3 );
    A.createRelationshipTo( C, REL2 );

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:

Define how to walk the path.

    final ArrayList<RelationshipType> orderedPathContext = new ArrayList<RelationshipType>();
    orderedPathContext.add( REL1 );
    orderedPathContext.add( withName( "REL2" ) );
    orderedPathContext.add( withName( "REL3" ) );
    TraversalDescription td = Traversal.description()
            .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 );
                }
            } );

Perform the traversal and print the result.

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

Which will output:

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

In this case we use a custom class to format the path output. This is how it’s done:

    static class PathPrinter implements Traversal.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;
        }
    }

For options regarding output of a `Path`, see the Traversal class.

| Note
The following examples use a deprecated traversal API. It shares the underlying implementation with the new traversal API, so performance-wise they are equal. The functionality it provides is very limited in comparison.

### 4.5.2. Old traversal API

This is the first graph we want to traverse into:

Figure 4.3. Matrix node space view

| Tip
The source code of the examples is found here: Matrix.java

Friends and friends of friends.

    private static Traverser getFriends( final Node person )
    {
        return person.traverse( Order.BREADTH_FIRST,
                StopEvaluator.END_OF_GRAPH,
                ReturnableEvaluator.ALL_BUT_START_NODE, RelTypes.KNOWS,
                Direction.OUTGOING );
    }

Let’s perform the actual traversal and print the results:

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

Which will give us the following output:

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

Who coded the Matrix?

    private static Traverser findHackers( final Node startNode )
    {
        return startNode.traverse( Order.BREADTH_FIRST,
                StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator()
        {
            @Override
            public boolean isReturnableNode(
                    final TraversalPosition currentPos )
            {
                return !currentPos.isStartNode()
                && currentPos.lastRelationshipTraversed()
                .isType( RelTypes.CODED_BY );
            }
        }, RelTypes.CODED_BY, Direction.OUTGOING, RelTypes.KNOWS,
        Direction.OUTGOING );
    }

Print out the result:

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

Now we know who coded the Matrix:

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

### 4.5.3. 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 4.4. Descendants Example Graph

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 = Traversal.description()
            .uniqueness( Uniqueness.NODE_PATH )
            .evaluator( new Evaluator()
    {
        @Override
        public Evaluation evaluate( Path path )
        {
            if ( path.endNode().equals( target ) )
            {
                return Evaluation.INCLUDE_AND_PRUNE;
            }
            return Evaluation.EXCLUDE_AND_CONTINUE;
        }
    } );

    Traverser results = td.traverse( start );

This will return the following paths:

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

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
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:

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

### 4.5.4. Social network

| Note
The following example uses the new experimental traversal API.

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.

| Tip
The source code of the example is found here: socnet

#### Simple social model

Figure 4.5. Social network data model

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:

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

    TraversalDescription traversal = Traversal.description().
            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;

Copyright © 2012 Neo Technology

Prev | Up | Next
| Home |