blob: 28e30367d6f752fd67b603c04b12900d51114b49 [file] [log] [blame]
/*
* 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 org.apache.hugegraph.traversal.algorithm;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.structure.HugeEdge;
import org.apache.hugegraph.structure.HugeVertex;
import org.apache.hugegraph.traversal.algorithm.steps.WeightedEdgeStep;
import org.apache.hugegraph.util.CollectionUtil;
import org.apache.hugegraph.util.E;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import jakarta.ws.rs.core.MultivaluedMap;
public class CustomizePathsTraverser extends HugeTraverser {
private final EdgeRecord edgeResults;
public CustomizePathsTraverser(HugeGraph graph) {
super(graph);
this.edgeResults = new EdgeRecord(false);
}
public static List<Path> topNPath(List<Path> paths,
boolean incr, long limit) {
paths.sort((p1, p2) -> {
WeightPath wp1 = (WeightPath) p1;
WeightPath wp2 = (WeightPath) p2;
int result = Double.compare(wp1.totalWeight(), wp2.totalWeight());
return incr ? result : -result;
});
if (limit == NO_LIMIT || paths.size() <= limit) {
return paths;
}
return paths.subList(0, (int) limit);
}
private static List<Node> sample(List<Node> nodes, long sample) {
if (nodes.size() <= sample) {
return nodes;
}
List<Node> result = newList((int) sample);
int size = nodes.size();
for (int random : CollectionUtil.randomSet(0, size, (int) sample)) {
result.add(nodes.get(random));
}
return result;
}
public List<Path> customizedPaths(Iterator<Vertex> vertices,
List<WeightedEdgeStep> steps, boolean sorted,
long capacity, long limit) {
E.checkArgument(vertices.hasNext(),
"The source vertices can't be empty");
E.checkArgument(!steps.isEmpty(), "The steps can't be empty");
checkCapacity(capacity);
checkLimit(limit);
MultivaluedMap<Id, Node> sources = newMultivalueMap();
while (vertices.hasNext()) {
HugeVertex vertex = (HugeVertex) vertices.next();
Node node = sorted ?
new WeightNode(vertex.id(), null, 0) :
new Node(vertex.id(), null);
sources.add(vertex.id(), node);
}
int stepNum = steps.size();
int pathCount = 0;
long access = 0;
MultivaluedMap<Id, Node> newVertices = null;
root:
for (WeightedEdgeStep step : steps) {
stepNum--;
newVertices = newMultivalueMap();
Iterator<Edge> edges;
// Traversal vertices of previous level
for (Map.Entry<Id, List<Node>> entry : sources.entrySet()) {
List<Node> adjacency = newList();
edges = this.edgesOfVertex(entry.getKey(), step.step());
while (edges.hasNext()) {
HugeEdge edge = (HugeEdge) edges.next();
this.edgeIterCounter.addAndGet(1L);
Id target = edge.id().otherVertexId();
this.edgeResults.addEdge(entry.getKey(), target, edge);
for (Node n : entry.getValue()) {
// If have loop, skip target
if (n.contains(target)) {
continue;
}
Node newNode;
if (sorted) {
double w = step.weightBy() != null ?
edge.value(step.weightBy().name()) :
step.defaultWeight();
newNode = new WeightNode(target, n, w);
} else {
newNode = new Node(target, n);
}
adjacency.add(newNode);
checkCapacity(capacity, ++access, "customized paths");
}
}
if (step.sample() > 0) {
// Sample current node's adjacent nodes
adjacency = sample(adjacency, step.sample());
}
// Add current node's adjacent nodes
for (Node node : adjacency) {
newVertices.add(node.id(), node);
// Avoid exceeding limit
if (stepNum == 0) {
if (limit != NO_LIMIT && !sorted &&
++pathCount >= limit) {
break root;
}
}
}
}
this.vertexIterCounter.addAndGet(sources.size());
// Re-init sources
sources = newVertices;
}
if (stepNum != 0) {
return ImmutableList.of();
}
List<Path> paths = newList();
if (newVertices == null) {
return ImmutableList.of();
}
for (List<Node> nodes : newVertices.values()) {
for (Node n : nodes) {
if (sorted) {
WeightNode wn = (WeightNode) n;
paths.add(new WeightPath(wn.path(), wn.weights()));
} else {
paths.add(new Path(n.path()));
}
}
}
return paths;
}
public EdgeRecord edgeResults() {
return edgeResults;
}
public static class WeightNode extends Node {
private final double weight;
public WeightNode(Id id, Node parent, double weight) {
super(id, parent);
this.weight = weight;
}
public List<Double> weights() {
List<Double> weights = newList();
WeightNode current = this;
while (current.parent() != null) {
weights.add(current.weight);
current = (WeightNode) current.parent();
}
Collections.reverse(weights);
return weights;
}
}
public static class WeightPath extends Path {
private final List<Double> weights;
private double totalWeight;
public WeightPath(List<Id> vertices,
List<Double> weights) {
super(vertices);
this.weights = weights;
this.calcTotalWeight();
}
public WeightPath(Id crosspoint, List<Id> vertices,
List<Double> weights) {
super(crosspoint, vertices);
this.weights = weights;
this.calcTotalWeight();
}
public List<Double> weights() {
return this.weights;
}
public double totalWeight() {
return this.totalWeight;
}
@Override
public void reverse() {
super.reverse();
Collections.reverse(this.weights);
}
@Override
public Map<String, Object> toMap(boolean withCrossPoint) {
if (withCrossPoint) {
return ImmutableMap.of("crosspoint", this.crosspoint(),
"objects", this.vertices(),
"weights", this.weights());
} else {
return ImmutableMap.of("objects", this.vertices(),
"weights", this.weights());
}
}
private void calcTotalWeight() {
double sum = 0;
for (double w : this.weights()) {
sum += w;
}
this.totalWeight = sum;
}
}
}