Skip to content

Commit

Permalink
Add Depth First Search repeat step option
Browse files Browse the repository at this point in the history
  • Loading branch information
krlohnes committed Apr 13, 2018
1 parent 973eda2 commit 861dbfd
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.SearchAlgo;
import org.apache.tinkerpop.gremlin.process.traversal.Translator;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
Expand Down Expand Up @@ -292,6 +293,7 @@ public final class CoreImports {
Collections.addAll(ENUM_IMPORTS, Order.values());
Collections.addAll(ENUM_IMPORTS, Pop.values());
Collections.addAll(ENUM_IMPORTS, Scope.values());
Collections.addAll(ENUM_IMPORTS, SearchAlgo.values());
Collections.addAll(ENUM_IMPORTS, T.values());
Collections.addAll(ENUM_IMPORTS, TraversalOptionParent.Pick.values());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.tinkerpop.gremlin.process.traversal;

/**
* The repeat step can behave either as a:
*
* {@link SearchAlgo#BFS}: Breadth First Search. This is the Default.
* {@link SearchAlgo#DFS}: Depth First Search
*
*/
public enum SearchAlgo {
BFS, DFS;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.tinkerpop.gremlin.process.traversal.Path;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.SearchAlgo;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
Expand Down Expand Up @@ -496,6 +497,19 @@ public default GraphTraversal<S, E> order(final Scope scope) {
return this.asAdmin().addStep(scope.equals(Scope.global) ? new OrderGlobalStep<>(this.asAdmin()) : new OrderLocalStep<>(this.asAdmin()));
}

/**
* Order the repeat step by depth first or breadth first
*
* @param algo either DFS or BFS
*
* @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#order-step" target="_blank">Reference Documentation - Order Step</a>
* @since 3.3.3
*/
public default GraphTraversal<S, E> order(final SearchAlgo algo) {
this.asAdmin().getBytecode().addStep(Symbols.order, algo);
return RepeatStep.addSearchAlgo(this, algo);
}

/**
* Map the {@link Element} to its associated properties given the provide property keys.
* If no property keys are provided, then all properties are emitted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.tinkerpop.gremlin.process.traversal.Path;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.SearchAlgo;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
Expand Down Expand Up @@ -232,6 +233,13 @@ public static <A> GraphTraversal<A, A> order(final Scope scope) {
return __.<A>start().order(scope);
}

/**
* @see GraphTraversal#order(SearchAlgo)
*/
public static <A> GraphTraversal<A, A> order(final SearchAlgo algo) {
return __.<A>start().order(algo);
}

/**
* @see GraphTraversal#properties(String...)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.branch;

import org.apache.tinkerpop.gremlin.process.traversal.SearchAlgo;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
Expand All @@ -31,6 +32,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
Expand All @@ -43,6 +45,7 @@ public final class RepeatStep<S> extends ComputerAwareStep<S, S> implements Trav
private Traversal.Admin<S, S> repeatTraversal = null;
private Traversal.Admin<S, ?> untilTraversal = null;
private Traversal.Admin<S, ?> emitTraversal = null;
private SearchAlgo searchAlgo = SearchAlgo.BFS;
public boolean untilFirst = false;
public boolean emitFirst = false;

Expand Down Expand Up @@ -79,6 +82,10 @@ public void setUntilTraversal(final Traversal.Admin<S, ?> untilTraversal) {
return this.untilTraversal;
}

public void setSearchAlgo(final SearchAlgo s) {
this.searchAlgo = s;
}

public void setEmitTraversal(final Traversal.Admin<S, ?> emitTraversal) {
if (null != this.emitTraversal)
throw new IllegalStateException("The repeat()-step already has its emit()-modulator declared: " + this);
Expand Down Expand Up @@ -179,6 +186,20 @@ public int hashCode() {
return result;
}

private final LinkedList<Traverser.Admin<S>> stashedStarts = new LinkedList<>();

private Traverser.Admin<S> nextStart() {
if (this.searchAlgo.equals(SearchAlgo.BFS)) {
return this.starts.next();
} else {
if (this.starts.hasNext()) {
return this.starts.next();
} else {
return this.stashedStarts.pop();
}
}
}

@Override
protected Iterator<Traverser.Admin<S>> standardAlgorithm() throws NoSuchElementException {
if (null == this.repeatTraversal)
Expand All @@ -188,7 +209,14 @@ protected Iterator<Traverser.Admin<S>> standardAlgorithm() throws NoSuchElementE
if (this.repeatTraversal.getEndStep().hasNext()) {
return this.repeatTraversal.getEndStep();
} else {
final Traverser.Admin<S> start = this.starts.next();
final Traverser.Admin<S> start = nextStart();
if (this.searchAlgo.equals(SearchAlgo.DFS)) {
final List<Traverser.Admin<S>> localStarts = new ArrayList<>();
while (this.starts.hasNext()) {
localStarts.add(this.starts.next());
}
stashedStarts.addAll(0, localStarts);
}
if (doUntil(start, true)) {
start.resetLoops();
return IteratorUtils.of(start);
Expand Down Expand Up @@ -241,6 +269,19 @@ public static <A, B, C extends Traversal<A, B>> C addRepeatToTraversal(final C t
return traversal;
}


public static <A, B, C extends Traversal<A, B>> C addSearchAlgo(final C traversal, final SearchAlgo s) {
final Step<?, B> step = traversal.asAdmin().getEndStep();
if (step instanceof RepeatStep) {
((RepeatStep<B>) step).setSearchAlgo(s);
} else {
final RepeatStep<B> repeatStep = new RepeatStep<>(traversal.asAdmin());
repeatStep.setSearchAlgo(s);
traversal.asAdmin().addStep(repeatStep);
}
return traversal;
}

public static <A, B, C extends Traversal<A, B>> C addUntilToTraversal(final C traversal, final Traversal.Admin<B, ?> untilPredicate) {
final Step<?, B> step = traversal.asAdmin().getEndStep();
if (step instanceof RepeatStep && null == ((RepeatStep) step).untilTraversal) {
Expand Down Expand Up @@ -273,11 +314,37 @@ public RepeatEndStep(final Traversal.Admin traversal) {
super(traversal);
}

final LinkedList<Traverser.Admin<S>> stashedStarts = new LinkedList<>();

private Traverser.Admin<S> nextStart(RepeatStep<S> repeatStep) {
if (repeatStep.searchAlgo.equals(SearchAlgo.BFS)) {
return this.starts.next();
} else {
if (this.starts.hasNext()) {
return this.starts.next();
} else {
return this.stashedStarts.pop();
}
}
}

@Override
public boolean hasNext() {
return super.hasNext() || this.stashedStarts.peek() != null;
}

@Override
protected Iterator<Traverser.Admin<S>> standardAlgorithm() throws NoSuchElementException {
final RepeatStep<S> repeatStep = (RepeatStep<S>) this.getTraversal().getParent();
while (true) {
final Traverser.Admin<S> start = this.starts.next();
final Traverser.Admin<S> start = nextStart(repeatStep);
if (repeatStep.searchAlgo.equals(SearchAlgo.DFS)) {
final List<Traverser.Admin<S>> localStarts = new ArrayList<>();
while (this.starts.hasNext()) {
localStarts.add(this.starts.next());
}
stashedStarts.addAll(0, localStarts);
}
start.incrLoops(this.getId());
if (repeatStep.doUntil(start, false)) {
start.resetLoops();
Expand Down Expand Up @@ -320,4 +387,4 @@ protected Iterator<Traverser.Admin<S>> computerAlgorithm() throws NoSuchElementE
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.SearchAlgo;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
Expand Down Expand Up @@ -155,6 +156,7 @@ static final class GraphSONModuleV3d0 extends GraphSONModule {
SackFunctions.Barrier.class,
TraversalOptionParent.Pick.class,
Scope.class,
SearchAlgo.class,
T.class).forEach(e -> put(e, e.getSimpleName()));
Arrays.asList(
ConnectiveStrategy.class,
Expand Down Expand Up @@ -227,6 +229,7 @@ protected GraphSONModuleV3d0(final boolean normalize) {
Pop.class,
SackFunctions.Barrier.class,
Scope.class,
SearchAlgo.class,
TraversalOptionParent.Pick.class,
T.class).forEach(e -> addSerializer(e, new TraversalSerializersV3d0.EnumJacksonSerializer()));
addSerializer(P.class, new TraversalSerializersV3d0.PJacksonSerializer());
Expand Down Expand Up @@ -267,6 +270,7 @@ protected GraphSONModuleV3d0(final boolean normalize) {
Pop.values(),
SackFunctions.Barrier.values(),
Scope.values(),
SearchAlgo.values(),
TraversalOptionParent.Pick.values(),
T.values()).flatMap(Stream::of).forEach(e -> addDeserializer(e.getClass(), new TraversalSerializersV3d0.EnumJacksonDeserializer(e.getDeclaringClass())));
addDeserializer(P.class, new TraversalSerializersV3d0.PJacksonDeserializer());
Expand Down Expand Up @@ -369,6 +373,7 @@ static final class GraphSONModuleV2d0 extends GraphSONModule {
SackFunctions.Barrier.class,
TraversalOptionParent.Pick.class,
Scope.class,
SearchAlgo.class,
T.class).forEach(e -> put(e, e.getSimpleName()));
Arrays.asList(
ConnectiveStrategy.class,
Expand Down Expand Up @@ -438,6 +443,7 @@ protected GraphSONModuleV2d0(final boolean normalize) {
Pop.class,
SackFunctions.Barrier.class,
Scope.class,
SearchAlgo.class,
TraversalOptionParent.Pick.class,
T.class).forEach(e -> addSerializer(e, new TraversalSerializersV2d0.EnumJacksonSerializer()));
addSerializer(P.class, new TraversalSerializersV2d0.PJacksonSerializer());
Expand Down Expand Up @@ -473,6 +479,7 @@ protected GraphSONModuleV2d0(final boolean normalize) {
Pop.values(),
SackFunctions.Barrier.values(),
Scope.values(),
SearchAlgo.values(),
TraversalOptionParent.Pick.values(),
T.values()).flatMap(Stream::of).forEach(e -> addDeserializer(e.getClass(), new TraversalSerializersV2d0.EnumJacksonDeserializer(e.getDeclaringClass())));
addDeserializer(P.class, new TraversalSerializersV2d0.PJacksonDeserializer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.SearchAlgo;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.util.Metrics;
Expand Down Expand Up @@ -135,6 +136,8 @@ else if (TraversalOptionParent.Pick.class.isAssignableFrom(c))
mapped = TraversalOptionParent.Pick.class;
else if (Scope.class.isAssignableFrom(c))
mapped = Scope.class;
else if (SearchAlgo.class.isAssignableFrom(c))
mapped = SearchAlgo.class;
else if (T.class.isAssignableFrom(c))
mapped = T.class;
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.SearchAlgo;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
Expand Down Expand Up @@ -168,6 +169,8 @@ else if (TraversalOptionParent.Pick.class.isAssignableFrom(c))
mapped = TraversalOptionParent.Pick.class;
else if (Scope.class.isAssignableFrom(c))
mapped = Scope.class;
else if (SearchAlgo.class.isAssignableFrom(c))
mapped = SearchAlgo.class;
else if (T.class.isAssignableFrom(c))
mapped = T.class;
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
import org.apache.tinkerpop.gremlin.process.traversal.SearchAlgo;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldStep;
Expand Down Expand Up @@ -303,6 +304,7 @@ public static List<TypeRegistration<?>> initV3d0Registrations() {
add(GryoTypeReg.of(Bytecode.Binding.class, 126, new GryoSerializersV3d0.BindingSerializer()));
add(GryoTypeReg.of(Order.class, 127));
add(GryoTypeReg.of(Scope.class, 128));
add(GryoTypeReg.of(SearchAlgo.class, 174)); // ***LAST ID***
add(GryoTypeReg.of(VertexProperty.Cardinality.class, 131));
add(GryoTypeReg.of(Column.class, 132));
add(GryoTypeReg.of(Pop.class, 133));
Expand Down Expand Up @@ -373,8 +375,7 @@ public static List<TypeRegistration<?>> initV3d0Registrations() {
add(GryoTypeReg.of(RangeGlobalStep.RangeBiOperator.class, 114));
add(GryoTypeReg.of(OrderGlobalStep.OrderBiOperator.class, 118));
add(GryoTypeReg.of(ProfileStep.ProfileBiOperator.class, 119));
add(GryoTypeReg.of(IndexedTraverserSet.VertexIndexedTraverserSet.class, 173)); // ***LAST ID***

add(GryoTypeReg.of(IndexedTraverserSet.VertexIndexedTraverserSet.class, 173));
// placeholder serializers for classes that don't live here in core. this will allow them to be used if
// present or ignored if the class isn't available. either way the registration numbers are held as
// placeholders so that the format stays stable
Expand Down Expand Up @@ -479,6 +480,7 @@ public static List<TypeRegistration<?>> initV1d0Registrations() {
add(GryoTypeReg.of(Bytecode.Binding.class, 126, new GryoSerializersV1d0.BindingSerializer()));
add(GryoTypeReg.of(Order.class, 127));
add(GryoTypeReg.of(Scope.class, 128));
add(GryoTypeReg.of(SearchAlgo.class, 174)); // ***LAST ID***
add(GryoTypeReg.of(VertexProperty.Cardinality.class, 131));
add(GryoTypeReg.of(Column.class, 132));
add(GryoTypeReg.of(Pop.class, 133));
Expand Down Expand Up @@ -552,7 +554,7 @@ public static List<TypeRegistration<?>> initV1d0Registrations() {
add(GryoTypeReg.of(MatchStep.CountMatchAlgorithm.class, 160));
add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 167));
// skip 171, 172 to sync with tp33
add(GryoTypeReg.of(IndexedTraverserSet.VertexIndexedTraverserSet.class, 173)); // ***LAST ID***
add(GryoTypeReg.of(IndexedTraverserSet.VertexIndexedTraverserSet.class, 173));
}};
}

Expand Down
15 changes: 15 additions & 0 deletions gremlin-test/features/branch/Repeat.feature
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,18 @@ Feature: Step - repeat()
| loop |
| loop |
| loop |

Scenario: g_V_hasXname_markoX_repeatXoutE_order_byXweight_decrX_inVX_emit_orderXDFSX
Given the modern graph
And the traversal of
"""
g.V().has("name", "marko").repeat(__.outE().order().by("weight", decr).inV()).emit().order(SearchAlgo.DFS)
"""
When iterated to list
Then the result should be ordered
| result |
| josh |
| ripple |
| lop |
| vadas |
| lop |
Loading

0 comments on commit 861dbfd

Please sign in to comment.