/*
* Copyright 2008 Network Engine for Objects in Lund AB [neotechnology.com]
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.graphalgo.shortestpath;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.neo4j.api.core.Direction;
import org.neo4j.api.core.Node;
import org.neo4j.api.core.PropertyContainer;
import org.neo4j.api.core.Relationship;
import org.neo4j.api.core.RelationshipType;
/**
* Breadth first search to find all shortest uniform paths from a node to all
* others. I.e. assume the cost 1 for all relationships. This can be done by
* Dijkstra with the right arguments, but this should be faster.
* @complexity This algorithm runs in O(m) time (not including the case when m
* is zero).
* @author Patrik Larsson
*/
public class SingleSourceShortestPathBFS implements
SingleSourceShortestPath<Integer>
{
protected Node startNode;
protected Direction relationShipDirection;
protected RelationshipType[] relationShipTypes;
protected HashMap<Node,Integer> distances = new HashMap<Node,Integer>();;
protected HashMap<Node,List<Relationship>> predecessors = new HashMap<Node,List<Relationship>>();
// Limits
protected long maxDepth = Long.MAX_VALUE;
protected long depth = 0;
LinkedList<Node> currentLayer = new LinkedList<Node>();;
LinkedList<Node> nextLayer = new LinkedList<Node>();
public SingleSourceShortestPathBFS( Node startNode,
Direction relationShipDirection, RelationshipType... relationShipTypes )
{
super();
this.startNode = startNode;
this.relationShipDirection = relationShipDirection;
this.relationShipTypes = relationShipTypes;
reset();
}
/**
* This sets the maximum depth to scan.
*/
public void limitDepth( long maxDepth )
{
this.maxDepth = maxDepth;
}
/**
* @see SingleSourceShortestPath
*/
public void setStartNode( Node node )
{
startNode = node;
reset();
}
/**
* @see SingleSourceShortestPath
*/
public void reset()
{
distances = new HashMap<Node,Integer>();
predecessors = new HashMap<Node,List<Relationship>>();
currentLayer = new LinkedList<Node>();;
nextLayer = new LinkedList<Node>();
currentLayer.add( startNode );
depth = 0;
}
/**
* @see SingleSourceShortestPath
*/
public Integer getCost( Node targetNode )
{
calculate( targetNode );
return distances.get( targetNode );
}
/**
* @see SingleSourceShortestPath
*/
public List<PropertyContainer> getPath( Node targetNode )
{
if ( targetNode == null )
{
throw new RuntimeException( "No end node defined" );
}
calculate( targetNode );
if ( !distances.containsKey( targetNode ) )
{
return null;
}
return Util.constructSinglePathToNode( targetNode, predecessors, true,
false );
}
/**
* @see SingleSourceShortestPath
*/
public List<Node> getPathAsNodes( Node targetNode )
{
if ( targetNode == null )
{
throw new RuntimeException( "No end node defined" );
}
calculate( targetNode );
if ( !distances.containsKey( targetNode ) )
{
return null;
}
return Util.constructSinglePathToNodeAsNodes( targetNode, predecessors,
true, false );
}
/**
* @see SingleSourceShortestPath
*/
public List<Relationship> getPathAsRelationships( Node targetNode )
{
if ( targetNode == null )
{
throw new RuntimeException( "No end node defined" );
}
calculate( targetNode );
if ( !distances.containsKey( targetNode ) )
{
return null;
}
return Util.constructSinglePathToNodeAsRelationships( targetNode,
predecessors, false );
}
/**
* @see SingleSourceShortestPath
*/
public List<List<PropertyContainer>> getPaths( Node targetNode )
{
if ( targetNode == null )
{
throw new RuntimeException( "No end node defined" );
}
calculate( targetNode );
if ( !distances.containsKey( targetNode ) )
{
return null;
}
return Util.constructAllPathsToNode( targetNode, predecessors, true,
false );
}
/**
* @see SingleSourceShortestPath
*/
public List<List<Node>> getPathsAsNodes( Node targetNode )
{
if ( targetNode == null )
{
throw new RuntimeException( "No end node defined" );
}
calculate( targetNode );
if ( !distances.containsKey( targetNode ) )
{
return null;
}
return Util.constructAllPathsToNodeAsNodes( targetNode, predecessors,
true, false );
}
/**
* @see SingleSourceShortestPath
*/
public List<List<Relationship>> getPathsAsRelationships( Node targetNode )
{
if ( targetNode == null )
{
throw new RuntimeException( "No end node defined" );
}
calculate( targetNode );
if ( !distances.containsKey( targetNode ) )
{
return null;
}
return Util.constructAllPathsToNodeAsRelationships( targetNode,
predecessors, false );
}
/**
* Iterator-style "next" method.
* @return True if advance was made. False if no more computation could be
* done.
*/
public boolean processNextNode()
{
// finished with current layer? increase depth
if ( currentLayer.isEmpty() )
{
if ( nextLayer.isEmpty() )
{
return false;
}
currentLayer = nextLayer;
nextLayer = new LinkedList<Node>();
++depth;
}
Node node = currentLayer.poll();
// Multiple paths to a certain node might make it appear several
// times, just process it once
if ( distances.containsKey( node ) )
{
return true;
}
// Put it in distances
distances.put( node, (int) depth );
// Follow all edges
for ( RelationshipType relationshipType : relationShipTypes )
{
for ( Relationship relationship : node.getRelationships(
relationshipType, relationShipDirection ) )
{
Node targetNode = relationship.getOtherNode( node );
// Are we going back into the already finished area?
// That would be more expensive.
if ( !distances.containsKey( targetNode ) )
{
// Put this into the next layer and the predecessors
nextLayer.add( targetNode );
List<Relationship> targetPreds = predecessors
.get( targetNode );
if ( targetPreds == null )
{
targetPreds = new LinkedList<Relationship>();
predecessors.put( targetNode, targetPreds );
}
targetPreds.add( relationship );
}
}
}
return true;
}
/**
* Internal calculate method that will do the calculation. This can however
* be called externally to manually trigger the calculation.
*/
public boolean calculate()
{
return calculate( null );
}
/**
* Internal calculate method that will run the calculation until either the
* limit is reached or a result has been generated for a given node.
*/
public boolean calculate( Node targetNode )
{
while ( depth <= maxDepth
&& (targetNode == null || !distances.containsKey( targetNode )) )
{
if ( !processNextNode() )
{
return false;
}
}
return true;
}
/**
* @see SingleSourceShortestPath
*/
public List<Node> getPredecessorNodes( Node node )
{
List<Node> result = new LinkedList<Node>();
List<Relationship> predecessorRelationShips = predecessors.get( node );
if ( predecessorRelationShips == null
|| predecessorRelationShips.size() == 0 )
{
return null;
}
for ( Relationship relationship : predecessorRelationShips )
{
result.add( relationship.getOtherNode( node ) );
}
return result;
}
/**
* @see SingleSourceShortestPath
*/
public Map<Node,List<Relationship>> getPredecessors()
{
calculate();
return predecessors;
}
/**
* @see SingleSourceShortestPath
*/
public Direction getDirection()
{
return relationShipDirection;
}
/**
* @see SingleSourceShortestPath
*/
public RelationshipType[] getRelationshipTypes()
{
return relationShipTypes;
}
}