Last active
October 18, 2016 20:45
-
-
Save lihaoyi/a14977ace3d22e89920350dbfdf44667 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
package test | |
package grepgit.core | |
import scala.collection.mutable | |
import scala.reflect.ClassTag | |
/** | |
* A Generator of elements of type [[A]]. | |
* | |
* [[Generator]] is basically the inverse of | |
* a `scala.Iterator`: instead of the core functionality being the pull-based | |
* `hasNext` and `next: T` methods, the core is based around the push-based | |
* `generate` method. `generate` is basically an extra-customizable version of | |
* `foreach`, which allows the person calling it to provide basic control-flow | |
* instructions to the upstream Generators. | |
* | |
* Unlike a `scala.Iterator`, subclasses of [[Generator]] can guarantee any clean | |
* up logic is performed by placing it after the `generate` call is made. | |
* | |
* Transformations on a [[Generator]] are lazy: calling methods like `filter` | |
* or `map` do not evaluate the entire Generator, but instead construct a new | |
* Generator that delegates to the original. The only methods that evaluate | |
* the [[Generator]] are the "Action" methods like | |
* `generate`/`foreach`/`find`, or the "Conversion" methods like `toArray` or | |
* similar. | |
* | |
* `generate` takes a function returning `Generator.Action` rather that | |
* `Unit`, as well as an additional `startingSkipped: Int` parameter. This allows | |
* a downstream Generator to provide basic control | |
* commands to the upstream Generators: e.g. [[Generator.End]] to cease | |
* enumeration of the upstream Generator. This allows it to avoid traversing and | |
* processing elements that the downstream Generator doesn't want/need to see | |
* anyway. | |
*/ | |
trait Generator[+A]{ | |
/** | |
* | |
* @param startingSkipped how many elements at the start of the Generator | |
* should be skipped when enumerating over it | |
* @param handleItem How to handle a single item: performs any desired side | |
* effects, and returns a [[Generator.Action]] that | |
* determines how to continue the enumeration. | |
* @return an integer stating how many skipped elements from the | |
* `startingSkipped` input remain to be skipped after this | |
* `generate` call has completed. | |
*/ | |
def generate(startingSkipped: Int)(handleItem: A => Generator.Action): Int | |
// Actions | |
def foreach(f: A => Unit): Unit = generate(0){ x => | |
f(x) | |
Generator.Continue | |
} | |
def find(f: A => Boolean): Option[A] = { | |
var result: Option[A] = None | |
generate(0){ t => | |
if (!f(t)) Generator.Continue | |
else{ | |
result = Some(t) | |
Generator.End | |
} | |
} | |
result | |
} | |
def exists(f: A => Boolean) = find(!f(_)).isDefined | |
def forall(f: A => Boolean) = !exists(f) | |
def count(f: A => Boolean) = { | |
var result = 0 | |
generate(0){ t => | |
if (f(t)) result += 1 | |
Generator.Continue | |
} | |
result | |
} | |
def foldLeft[B](start: B)(f: (B, A) => B): B = { | |
var result = start | |
generate(0){ t => | |
result = f(result, t) | |
Generator.Continue | |
} | |
result | |
} | |
// Builders | |
def filter(pred: A => Boolean): Generator[A] = new Generator.Filtered(pred, this) | |
def map[B](func: A => B): Generator[B] = new Generator.Mapped[B, A](func, this) | |
def flatMap[B](func: A => Generator[B]): Generator[B] = new Generator.FlatMapped[B, A](func, this) | |
def slice(start: Int, end: Int): Generator[A] = new Generator.Sliced(start, end, this) | |
def take(n: Int) = slice(0, n) | |
def drop(n: Int) = slice(n, Int.MaxValue) | |
def takeWhile(pred: A => Boolean): Generator[A] = new Generator.TakeWhile(pred, this) | |
def dropWhile(pred: A => Boolean): Generator[A] = new Generator.DropWhile(pred, this) | |
def zipWithIndex = new Generator.ZipWithIndexed(this) | |
def zip[B](other: Iterable[B]) = new Generator.Zipped(this, other) | |
// Conversions | |
def head = take(1).toSeq.head | |
def toBuffer[B >: A]: mutable.Buffer[B] = { | |
val arr = mutable.Buffer.empty[B] | |
foreach{arr.append(_)} | |
arr | |
} | |
def toArray[B >: A : ClassTag]: Array[B] = toBuffer.toArray | |
def toSeq: Seq[A] = toBuffer | |
def toList = toBuffer.toList | |
def toSet[B >: A] = toBuffer[B].toSet | |
def toVector = toBuffer.toVector | |
def mkString(start: String, sep: String, end: String): String = { | |
val sb = new StringBuilder | |
sb.append(start) | |
var first = true | |
foreach { x => | |
sb.append(x) | |
if (!first) { | |
sb.append(sep) | |
} | |
first = false | |
} | |
sb.append(end) | |
sb.toString() | |
} | |
def mkString(sep: String): String = mkString("", sep, "") | |
def mkString: String = mkString("") | |
} | |
object Generator{ | |
sealed trait Action | |
object End extends Action | |
object Continue extends Action | |
implicit def apply[M[_], T](t: M[T])(implicit convert: (M[T] => Iterable[T])): Generator[T] = new Generator[T]{ | |
def generate(startingSkipped: Int)(f: T => Generator.Action) = { | |
var done = false | |
val iterator = convert(t).iterator | |
var skippedSoFar = 0 | |
while (skippedSoFar < startingSkipped && iterator.hasNext) { | |
skippedSoFar += 1 | |
iterator.next() | |
} | |
val remainingSkipped = startingSkipped - skippedSoFar | |
while (!done && iterator.hasNext) { | |
f(iterator.next()) match { | |
case End => done = true | |
case Continue => // do nothing | |
} | |
} | |
remainingSkipped | |
} | |
override def toString = s"Generator($t)" | |
} | |
class ZipWithIndexed[+T](inner: Generator[T]) extends Generator[(T, Int)] { | |
def generate(startingSkipped: Int)(f: ((T, Int)) => Generator.Action) = { | |
var i = 0 | |
inner.generate(startingSkipped){t => | |
val res = f(t, i) | |
i += 1 | |
res | |
} | |
} | |
override def toString = s"$inner.zipWithIndex" | |
} | |
class Zipped[+T, V](inner: Generator[T], other: Iterable[V]) extends Generator[(T, V)] { | |
def generate(startingSkipped: Int)(f: ((T, V)) => Generator.Action) = { | |
val iter = other.iterator | |
inner.generate(startingSkipped){t => | |
if (!iter.hasNext) Generator.End | |
else f(t, iter.next()) | |
} | |
} | |
override def toString = s"$inner.zip($other)" | |
} | |
class Filtered[+T](pred: T => Boolean, inner: Generator[T]) extends Generator[T]{ | |
def generate(startingSkipped: Int)(f: T => Generator.Action) = { | |
inner.generate(startingSkipped){t => if (pred(t)) f(t) else Generator.Continue} | |
} | |
override def toString = s"$inner.filter($pred)" | |
} | |
class Mapped[+T, V](func: V => T, inner: Generator[V]) extends Generator[T]{ | |
def generate(startingSkipped: Int)(f: T => Generator.Action) = { | |
inner.generate(startingSkipped){t => f(func(t))} | |
} | |
override def toString = s"$inner.map($func)" | |
} | |
class FlatMapped[+T, V](func: V => Generator[T], inner: Generator[V]) extends Generator[T]{ | |
def generate(startingSkipped: Int)(f: T => Generator.Action) = { | |
var remainingSkipped = startingSkipped | |
inner.generate(0){ outerT => | |
remainingSkipped = func(outerT).generate(remainingSkipped){ innerT => | |
f(innerT) | |
} | |
Generator.Continue | |
} | |
} | |
override def toString = s"$inner.map($func)" | |
} | |
class Sliced[+T](start: Int, end: Int, inner: Generator[T]) extends Generator[T]{ | |
def generate(startingSkipped: Int)(f: T => Generator.Action) = { | |
var count = 0 | |
inner.generate(startingSkipped + start){t => | |
if (count < end - start - startingSkipped){ | |
count += 1 | |
f(t) | |
}else{ | |
Generator.End | |
} | |
} | |
} | |
override def toString = s"$inner.slice($start, $end)" | |
} | |
class TakeWhile[+T](pred: T => Boolean, inner: Generator[T]) extends Generator[T]{ | |
def generate(startingSkipped: Int)(f: T => Generator.Action) = { | |
inner.generate(startingSkipped){t => | |
if (pred(t)) { | |
f(t) | |
} else { | |
Generator.End | |
} | |
} | |
} | |
override def toString = s"$inner.takeWhile($pred)" | |
} | |
class DropWhile[+T](pred: T => Boolean, inner: Generator[T]) extends Generator[T]{ | |
def generate(startingSkipped: Int)(f: T => Generator.Action) = { | |
var started = false | |
inner.generate(startingSkipped){t => | |
if (!started) { | |
if (pred(t)) Generator.Continue | |
else { | |
started = true | |
Generator.Continue | |
} | |
}else f(t) | |
} | |
} | |
override def toString = s"$inner.dropWhile($pred)" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment