Skip to content

Instantly share code, notes, and snippets.

@lihaoyi
Last active October 18, 2016 20:45
Show Gist options
  • Save lihaoyi/a14977ace3d22e89920350dbfdf44667 to your computer and use it in GitHub Desktop.
Save lihaoyi/a14977ace3d22e89920350dbfdf44667 to your computer and use it in GitHub Desktop.
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