-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bring back path tracking - used in codescience (#167)
- Loading branch information
1 parent
3adb3bb
commit c93a366
Showing
6 changed files
with
255 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
core/src/main/scala/flatgraph/traversal/PathAwareRepeatStep.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package flatgraph.traversal | ||
|
||
import flatgraph.traversal.RepeatBehaviour.SearchAlgorithm | ||
|
||
import scala.collection.{mutable, Iterator} | ||
|
||
object PathAwareRepeatStep { | ||
import RepeatStep._ | ||
|
||
case class WorklistItem[A](traversal: Iterator[A], depth: Int) | ||
|
||
/** @see | ||
* [[Traversal.repeat]] for a detailed overview | ||
* | ||
* Implementation note: using recursion results in nicer code, but uses the JVM stack, which only has enough space for ~10k steps. So | ||
* instead, this uses a programmatic Stack which is semantically identical. The RepeatTraversalTests cover this case. | ||
*/ | ||
def apply[A](repeatTraversal: Iterator[A] => Iterator[A], behaviour: RepeatBehaviour[A]): A => PathAwareTraversal[A] = { (element: A) => | ||
new PathAwareTraversal[A](new Iterator[(A, Vector[Any])] { | ||
val visited = mutable.Set.empty[A] | ||
val emitSack: mutable.Queue[(A, Vector[Any])] = mutable.Queue.empty | ||
val worklist: Worklist[WorklistItem[A]] = behaviour.searchAlgorithm match { | ||
case SearchAlgorithm.DepthFirst => new LifoWorklist() | ||
case SearchAlgorithm.BreadthFirst => new FifoWorklist() | ||
} | ||
|
||
worklist.addItem(WorklistItem(new PathAwareTraversal(Iterator.single((element, Vector.empty))), 0)) | ||
|
||
def hasNext: Boolean = { | ||
if (emitSack.isEmpty) { | ||
// this may add elements to the emit sack and/or modify the worklist | ||
traverseOnWorklist | ||
} | ||
emitSack.nonEmpty || worklistTopHasNext | ||
} | ||
|
||
private def traverseOnWorklist: Unit = { | ||
var stop = false | ||
while (worklist.nonEmpty && !stop) { | ||
val WorklistItem(trav0, depth) = worklist.head | ||
val trav = trav0.asInstanceOf[PathAwareTraversal[A]].wrapped | ||
if (trav.isEmpty) worklist.removeHead() | ||
else if (behaviour.maxDepthReached(depth)) stop = true | ||
else { | ||
val (element, path1) = trav.next() | ||
if (behaviour.dedupEnabled) visited.addOne(element) | ||
|
||
if ( // `while/repeat` behaviour, i.e. check every time | ||
behaviour.whileConditionIsDefinedAndEmpty(element) || | ||
// `repeat/until` behaviour, i.e. only checking the `until` condition from depth 1 | ||
(depth > 0 && behaviour.untilConditionReached(element)) | ||
) { | ||
// we just consumed an element from the traversal, so in lieu adding to the emit sack | ||
emitSack.enqueue((element, path1)) | ||
stop = true | ||
} else { | ||
val nextLevelTraversal = { | ||
val repeat = | ||
repeatTraversal(new PathAwareTraversal(Iterator.single((element, path1)))) | ||
if (behaviour.dedupEnabled) repeat.filterNot(visited.contains) | ||
else repeat | ||
} | ||
worklist.addItem(WorklistItem(nextLevelTraversal, depth + 1)) | ||
|
||
if (behaviour.shouldEmit(element, depth)) | ||
emitSack.enqueue((element, path1)) | ||
|
||
if (emitSack.nonEmpty) | ||
stop = true | ||
} | ||
} | ||
} | ||
} | ||
|
||
private def worklistTopHasNext: Boolean = | ||
worklist.nonEmpty && worklist.head.traversal.hasNext | ||
|
||
override def next(): (A, Vector[Any]) = { | ||
val result = { | ||
if (emitSack.nonEmpty) emitSack.dequeue() | ||
else if (worklistTopHasNext) { | ||
worklist.head.traversal.asInstanceOf[PathAwareTraversal[A]].wrapped.next() | ||
} else throw new NoSuchElementException("next on empty iterator") | ||
} | ||
if (behaviour.dedupEnabled) visited.addOne(result._1) | ||
result | ||
} | ||
}) | ||
} | ||
|
||
} |
80 changes: 80 additions & 0 deletions
80
core/src/main/scala/flatgraph/traversal/PathAwareTraversal.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package flatgraph.traversal | ||
|
||
import scala.collection.{IterableOnce, Iterator} | ||
|
||
class PathAwareTraversal[A](val wrapped: Iterator[(A, Vector[Any])]) extends Iterator[A] { | ||
override def hasNext: Boolean = wrapped.hasNext | ||
|
||
override def next(): A = wrapped.next()._1 | ||
|
||
override def map[B](f: A => B): PathAwareTraversal[B] = new PathAwareTraversal[B](wrapped.map { case (a, p) => | ||
(f(a), p.appended(a)) | ||
}) | ||
|
||
override def flatMap[B](f: A => IterableOnce[B]): PathAwareTraversal[B] = | ||
new PathAwareTraversal[B](wrapped.flatMap { case (a, p) => | ||
val ap = p.appended(a) | ||
f(a).iterator.map { | ||
(_, ap) | ||
} | ||
}) | ||
|
||
override def distinctBy[B](f: A => B): PathAwareTraversal[A] = new PathAwareTraversal[A](wrapped.distinctBy { case (a, p) => | ||
f(a) | ||
}) | ||
|
||
override def collect[B](pf: PartialFunction[A, B]): PathAwareTraversal[B] = flatMap(pf.lift) | ||
|
||
override def filter(p: A => Boolean): PathAwareTraversal[A] = new PathAwareTraversal(wrapped.filter(ap => p(ap._1))) | ||
|
||
override def filterNot(p: A => Boolean): PathAwareTraversal[A] = new PathAwareTraversal(wrapped.filterNot(ap => p(ap._1))) | ||
|
||
override def duplicate: (Iterator[A], Iterator[A]) = { | ||
val tmp = wrapped.duplicate | ||
(new PathAwareTraversal(tmp._1), new PathAwareTraversal(tmp._2)) | ||
} | ||
|
||
private[traversal] def _union[B](traversals: (Iterator[A] => Iterator[B])*): Iterator[B] = | ||
new PathAwareTraversal(wrapped.flatMap { case (a, p) => | ||
traversals.iterator.flatMap { inner => | ||
inner(new PathAwareTraversal(Iterator.single((a, p)))) match { | ||
case stillPathAware: PathAwareTraversal[B] => stillPathAware.wrapped | ||
// do we really want to allow the following, or is it an error? | ||
case notPathAware => notPathAware.iterator.map { (b: B) => (b, p.appended(a)) } | ||
} | ||
} | ||
}) | ||
|
||
private[traversal] def _choose[BranchOn >: Null, NewEnd](on: Iterator[A] => Iterator[BranchOn])( | ||
options: PartialFunction[BranchOn, Iterator[A] => Iterator[NewEnd]] | ||
): Iterator[NewEnd] = | ||
new PathAwareTraversal(wrapped.flatMap { case (a, p) => | ||
val branchOnValue: BranchOn = on(Iterator.single(a)).nextOption().getOrElse(null) | ||
options | ||
.applyOrElse(branchOnValue, (failState: BranchOn) => (unused: Iterator[A]) => Iterator.empty[NewEnd]) | ||
.apply(new PathAwareTraversal(Iterator.single((a, p)))) match { | ||
case stillPathAware: PathAwareTraversal[NewEnd] => stillPathAware.wrapped | ||
// do we really want to allow the following, or is it an error? | ||
case notPathAware => notPathAware.iterator.map { (b: NewEnd) => (b, p.appended(a)) } | ||
} | ||
}) | ||
|
||
private[traversal] def _coalesce[NewEnd](options: (Iterator[A] => Iterator[NewEnd])*): Iterator[NewEnd] = | ||
new PathAwareTraversal(wrapped.flatMap { case (a, p) => | ||
options.iterator | ||
.map { inner => | ||
inner(new PathAwareTraversal(Iterator.single((a, p)))) match { | ||
case stillPathAware: PathAwareTraversal[NewEnd] => stillPathAware.wrapped | ||
// do we really want to allow the following, or is it an error? | ||
case notPathAware => notPathAware.iterator.map { (b: NewEnd) => (b, p.appended(a)) } | ||
} | ||
} | ||
.find(_.nonEmpty) | ||
.getOrElse(Iterator.empty) | ||
}) | ||
|
||
private[traversal] def _sideEffect(f: A => _): PathAwareTraversal[A] = new PathAwareTraversal(wrapped.map { case (a, p) => | ||
f(a); (a, p) | ||
}) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.