blob: 9b89be5b62d95ac53e5c59e4e85d4942574533e9 [file] [log] [blame]
/*
* Copyright 2017 HugeGraph Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.baidu.hugegraph.traversal.algorithm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.tinkerpop.gremlin.structure.Edge;
import com.baidu.hugegraph.HugeGraph;
import com.baidu.hugegraph.backend.id.Id;
import com.baidu.hugegraph.backend.query.QueryResults;
import com.baidu.hugegraph.structure.HugeEdge;
import com.baidu.hugegraph.type.define.Directions;
import com.baidu.hugegraph.util.CollectionUtil;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.InsertionOrderUtil;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
public class SingleSourceShortestPathTraverser extends HugeTraverser {
public SingleSourceShortestPathTraverser(HugeGraph graph) {
super(graph);
}
public WeightedPaths singleSourceShortestPaths(Id sourceV, Directions dir,
String label, String weight,
long degree, long skipDegree,
long capacity, long limit) {
E.checkNotNull(sourceV, "source vertex id");
this.checkVertexExist(sourceV, "source vertex");
E.checkNotNull(dir, "direction");
checkDegree(degree);
checkCapacity(capacity);
checkSkipDegree(skipDegree, degree, capacity);
checkLimit(limit);
Id labelId = this.getEdgeLabelId(label);
Traverser traverser = new Traverser(sourceV, dir, labelId, weight,
degree, skipDegree, capacity,
limit);
while (true) {
// Found, reach max depth or reach capacity, stop searching
traverser.forward();
if (traverser.done()) {
return traverser.shortestPaths();
}
checkCapacity(traverser.capacity, traverser.size, "shortest path");
}
}
public NodeWithWeight weightedShortestPath(Id sourceV, Id targetV,
Directions dir, String label,
String weight, long degree,
long skipDegree, long capacity) {
E.checkNotNull(sourceV, "source vertex id");
this.checkVertexExist(sourceV, "source vertex");
E.checkNotNull(dir, "direction");
E.checkNotNull(weight, "weight property");
checkDegree(degree);
checkCapacity(capacity);
checkSkipDegree(skipDegree, degree, capacity);
Id labelId = this.getEdgeLabelId(label);
Traverser traverser = new Traverser(sourceV, dir, labelId, weight,
degree, skipDegree, capacity,
NO_LIMIT);
while (true) {
traverser.forward();
Map<Id, NodeWithWeight> results = traverser.shortestPaths();
if (results.containsKey(targetV) || traverser.done()) {
return results.get(targetV);
}
checkCapacity(traverser.capacity, traverser.size, "shortest path");
}
}
private class Traverser {
private WeightedPaths findingNodes = new WeightedPaths();
private WeightedPaths foundNodes = new WeightedPaths();
private Set<NodeWithWeight> sources;
private Id source;
private final Directions direction;
private final Id label;
private final String weight;
private final long degree;
private final long skipDegree;
private final long capacity;
private final long limit;
private long size;
private boolean done = false;
public Traverser(Id sourceV, Directions dir, Id label, String weight,
long degree, long skipDegree, long capacity,
long limit) {
this.source = sourceV;
this.sources = ImmutableSet.of(new NodeWithWeight(
0D, new Node(sourceV, null)));
this.direction = dir;
this.label = label;
this.weight = weight;
this.degree = degree;
this.skipDegree = skipDegree;
this.capacity = capacity;
this.limit = limit;
this.size = 0L;
}
/**
* Search forward from source
*/
public void forward() {
long degree = this.skipDegree > 0L ? this.skipDegree : this.degree;
for (NodeWithWeight node : this.sources) {
Iterator<Edge> edges = edgesOfVertex(node.node().id(),
this.direction,
this.label, degree);
edges = this.skipSuperNodeIfNeeded(edges);
while (edges.hasNext()) {
HugeEdge edge = (HugeEdge) edges.next();
Id target = edge.id().otherVertexId();
if (this.foundNodes.containsKey(target) ||
this.source.equals(target)) {
// Already find shortest path for target, skip
continue;
}
double currentWeight = this.edgeWeight(edge);
double weight = currentWeight + node.weight();
NodeWithWeight nw = new NodeWithWeight(weight, target, node);
NodeWithWeight exist = this.findingNodes.get(target);
if (exist == null || weight < exist.weight()) {
/*
* There are 2 scenarios to update finding nodes:
* 1. The 'target' found first time, add current path
* 2. Already exist path for 'target' and current
* path is shorter, update path for 'target'
*/
this.findingNodes.put(target, nw);
}
}
}
Map<Id, NodeWithWeight> sorted = CollectionUtil.sortByValue(
this.findingNodes, true);
double minWeight = 0;
Set<NodeWithWeight> newSources = InsertionOrderUtil.newSet();
for (Map.Entry<Id, NodeWithWeight> entry : sorted.entrySet()) {
Id id = entry.getKey();
NodeWithWeight wn = entry.getValue();
if (minWeight == 0) {
minWeight = wn.weight();
} else if (wn.weight() > minWeight) {
break;
}
// Move shortest paths from 'findingNodes' to 'foundNodes'
this.foundNodes.put(id, wn);
if (this.limit != NO_LIMIT &&
this.foundNodes.size() >= this.limit) {
this.done = true;
return;
}
this.findingNodes.remove(id);
// Re-init 'sources'
newSources.add(wn);
}
this.sources = newSources;
if (this.sources.isEmpty()) {
this.done = true;
}
}
public boolean done() {
return this.done;
}
public WeightedPaths shortestPaths() {
return this.foundNodes;
}
private double edgeWeight(HugeEdge edge) {
double edgeWeight;
if (this.weight == null ||
!edge.property(this.weight).isPresent()) {
edgeWeight = 1.0;
} else {
edgeWeight = edge.value(this.weight);
}
return edgeWeight;
}
private Iterator<Edge> skipSuperNodeIfNeeded(Iterator<Edge> edges) {
if (this.skipDegree <= 0L) {
return edges;
}
List<Edge> edgeList = new ArrayList<>();
int count = 0;
while (edges.hasNext()) {
if (count < this.degree) {
edgeList.add(edges.next());
}
if (count >= this.skipDegree) {
return QueryResults.emptyIterator();
}
count++;
}
return edgeList.iterator();
}
}
public static class NodeWithWeight implements Comparable<NodeWithWeight> {
private final double weight;
private final Node node;
public NodeWithWeight(double weight, Node node) {
this.weight = weight;
this.node = node;
}
public NodeWithWeight(double weight, Id id, NodeWithWeight prio) {
this(weight, new Node(id, prio.node()));
}
public double weight() {
return weight;
}
public Node node() {
return this.node;
}
public Map<String, Object> toMap() {
return ImmutableMap.of("weight", this.weight,
"vertices", this.node().path());
}
@Override
public int compareTo(NodeWithWeight other) {
return Double.compare(this.weight, other.weight);
}
}
public static class WeightedPaths extends LinkedHashMap<Id, NodeWithWeight> {
private static final long serialVersionUID = -313873642177730993L;
public Set<Id> vertices() {
Set<Id> vertices = new HashSet<>();
vertices.addAll(this.keySet());
for (NodeWithWeight nw : this.values()) {
vertices.addAll(nw.node().path());
}
return vertices;
}
public Map<Id, Map<String, Object>> toMap() {
Map<Id, Map<String, Object>> results = new HashMap<>();
for (Map.Entry<Id, NodeWithWeight> entry : this.entrySet()) {
Id source = entry.getKey();
NodeWithWeight nw = entry.getValue();
Map<String, Object> result = nw.toMap();
results.put(source, result);
}
return results;
}
}
}