diff --git a/hugegraph-api/pom.xml b/hugegraph-api/pom.xml index 98dc09a54e..8ea074ee5e 100644 --- a/hugegraph-api/pom.xml +++ b/hugegraph-api/pom.xml @@ -86,7 +86,7 @@ - 0.36.0.0 + 0.37.0.0 diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java index 841a177436..c702e0e2dd 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/ShortestPathAPI.java @@ -41,11 +41,11 @@ import com.baidu.hugegraph.server.RestServer; import com.baidu.hugegraph.traversal.algorithm.ShortestPathTraverser; import com.baidu.hugegraph.type.define.Directions; +import com.baidu.hugegraph.util.E; import com.baidu.hugegraph.util.Log; import com.codahale.metrics.annotation.Timed; -import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; -import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_DEGREE; +import static com.baidu.hugegraph.traversal.algorithm.HugeTraverser.*; @Path("graphs/{graph}/traversers/shortestpath") @Singleton @@ -65,13 +65,15 @@ public String get(@Context GraphManager manager, @QueryParam("max_depth") int depth, @QueryParam("max_degree") @DefaultValue(DEFAULT_DEGREE) long degree, + @QueryParam("skip_degree") + @DefaultValue("0") long skipDegree, @QueryParam("capacity") @DefaultValue(DEFAULT_CAPACITY) long capacity) { LOG.debug("Graph [{}] get shortest path from '{}', to '{}' with " + "direction {}, edge label {}, max depth '{}', " + - "max degree '{}' and capacity '{}'", + "max degree '{}', skipped degree '{}' and capacity '{}'", graph, source, target, direction, edgeLabel, depth, - degree, capacity); + degree, skipDegree, capacity); Id sourceId = VertexAPI.checkAndParseVertexId(source); Id targetId = VertexAPI.checkAndParseVertexId(target); @@ -82,7 +84,7 @@ public String get(@Context GraphManager manager, ShortestPathTraverser traverser = new ShortestPathTraverser(g); List path = traverser.shortestPath(sourceId, targetId, dir, edgeLabel, depth, degree, - capacity); + skipDegree, capacity); return manager.serializer(g).writeIds("path", path); } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java index a0dd5e71c6..ed51000a36 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java @@ -82,10 +82,11 @@ public final class ApiVersion { * [0.34] Issue-307: Let VertexAPI use simplified property serializer * [0.35] Issue-287: Support pagination when do index query * [0.36] Issue-360: Support paging for scan api + * [0.37] Issue-391: Add skip_super_node for shortest path */ // The second parameter of Version.of() is for IDE running without JAR - public static final Version VERSION = Version.of(ApiVersion.class, "0.36"); + public static final Version VERSION = Version.of(ApiVersion.class, "0.37"); public static final void check() { // Check version of hugegraph-core. Firstly do check from version 0.3 diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java index f283808ffc..8bc692c1e4 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/ShortestPathTraverser.java @@ -19,12 +19,14 @@ package com.baidu.hugegraph.traversal.algorithm; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; import com.baidu.hugegraph.HugeGraph; import com.baidu.hugegraph.backend.id.Id; @@ -41,13 +43,14 @@ public ShortestPathTraverser(HugeGraph graph) { public List shortestPath(Id sourceV, Id targetV, Directions dir, String label, int depth, long degree, - long capacity) { + long skipDegree, long capacity) { E.checkNotNull(sourceV, "source vertex id"); E.checkNotNull(targetV, "target vertex id"); E.checkNotNull(dir, "direction"); checkPositive(depth, "max depth"); checkDegree(degree); checkCapacity(capacity); + checkSkipDegree(skipDegree, degree, capacity); if (sourceV.equals(targetV)) { return ImmutableList.of(sourceV); @@ -55,7 +58,7 @@ public List shortestPath(Id sourceV, Id targetV, Directions dir, Id labelId = this.getEdgeLabelId(label); Traverser traverser = new Traverser(sourceV, targetV, dir, labelId, - degree, capacity); + degree, skipDegree, capacity); List path; while (true) { // Found, reach max depth or reach capacity, stop searching @@ -73,6 +76,25 @@ public List shortestPath(Id sourceV, Id targetV, Directions dir, return path; } + private static void checkSkipDegree(long skipDegree, long degree, + long capacity) { + E.checkArgument(skipDegree >= 0L, + "The skipped degree must be >= 0, but got '%s'", + skipDegree); + if (capacity != NO_LIMIT) { + E.checkArgument(degree != NO_LIMIT && degree < capacity, + "The degree must be < capacity"); + E.checkArgument(skipDegree < capacity, + "The skipped degree must be < capacity"); + } + if (skipDegree > 0L) { + E.checkArgument(degree != NO_LIMIT && skipDegree >= degree, + "The skipped degree must be >= degree, " + + "but got skipped degree '%s' and degree '%s'", + skipDegree, degree); + } + } + private class Traverser { // TODO: change Map to Set to reduce memory cost @@ -82,16 +104,18 @@ private class Traverser { private final Directions direction; private final Id label; private final long degree; + private final long skipDegree; private final long capacity; private long size; - public Traverser(Id sourceV, Id targetV, Directions dir, - Id label, long degree, long capacity) { + public Traverser(Id sourceV, Id targetV, Directions dir, Id label, + long degree, long skipDegree, long capacity) { this.sources.put(sourceV, new Node(sourceV)); this.targets.put(targetV, new Node(targetV)); this.direction = dir; this.label = label; this.degree = degree; + this.skipDegree = skipDegree; this.capacity = capacity; this.size = 0L; } @@ -101,16 +125,21 @@ public Traverser(Id sourceV, Id targetV, Directions dir, */ public List forward() { Map newVertices = newMap(); + long degree = this.skipDegree > 0L ? this.skipDegree : this.degree; // Traversal vertices of previous level for (Node v : this.sources.values()) { Iterator edges = edgesOfVertex(v.id(), this.direction, - this.label, this.degree); + this.label, degree); + edges = this.skipSuperNodeIfNeeded(edges); while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); Id target = edge.id().otherVertexId(); // If cross point exists, shortest path found, concat them if (this.targets.containsKey(target)) { + if (this.superNode(target, this.direction)) { + continue; + } return v.joinPath(this.targets.get(target)); } @@ -140,17 +169,22 @@ public List forward() { */ public List backward() { Map newVertices = newMap(); + long degree = this.skipDegree > 0L ? this.skipDegree : this.degree; Directions opposite = this.direction.opposite(); // Traversal vertices of previous level for (Node v : this.targets.values()) { Iterator edges = edgesOfVertex(v.id(), opposite, - this.label, this.degree); + this.label, degree); + edges = this.skipSuperNodeIfNeeded(edges); while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); Id target = edge.id().otherVertexId(); // If cross point exists, shortest path found, concat them if (this.sources.containsKey(target)) { + if (this.superNode(target, opposite)) { + continue; + } return v.joinPath(this.sources.get(target)); } @@ -174,5 +208,30 @@ public List backward() { return PATH_NONE; } + + private Iterator skipSuperNodeIfNeeded(Iterator edges) { + if (this.skipDegree <= 0L) { + return edges; + } + List edgeList = new ArrayList<>(); + for (int i = 1; edges.hasNext(); i++) { + if (i <= this.degree) { + edgeList.add(edges.next()); + } + if (i >= this.skipDegree) { + return Collections.emptyIterator(); + } + } + return edgeList.iterator(); + } + + private boolean superNode(Id vertex, Directions direction) { + if (this.skipDegree <= 0L) { + return false; + } + Iterator edges = edgesOfVertex(vertex, direction, + this.label, this.skipDegree); + return IteratorUtils.count(edges) >= this.skipDegree; + } } }