Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Range and Diet to generate values lazily #74

Merged
merged 2 commits into from Dec 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
364 changes: 141 additions & 223 deletions core/src/main/scala/dogs/Diet.scala

Large diffs are not rendered by default.

183 changes: 66 additions & 117 deletions core/src/main/scala/dogs/Range.scala
Expand Up @@ -3,52 +3,73 @@ package dogs
import cats.Show
import dogs.Predef._
import cats.Order
import scala.annotation.tailrec

/**
* Represent a range [x, y] that can be generated by using discrete operations
*/
sealed class Range[A](val start: A, val end: A) {
* Represent an inclusive range [x, y] that can be generated by using discrete operations
*/
final case class Range[A](val start: A, val end: A) {
import Option._

/**
* Calculate the difference with range.
*
* It returns a tuple with the difference to the right and to the left of range.
*
* It basically calculates what is to the left of range that is in this and what is to the right
* of range that is in this (in both cases it does not include elements in range)
*/
def -(range: Range[A])(implicit discrete: Enum[A], order: Order[A]): (Range[A], Range[A]) = {
if (order.compare(end, range.start) < 0) {
(this, Range.empty[A]())
}
else if (order.compare(start, range.end) > 0) {
(Range.empty[A](), this)
* Subtract a Range from this range.
* The result will be 0, 1 or 2 ranges
*/
def -(range: Range[A])(implicit enum: Enum[A], order: Order[A]): Option[(Range[A], Option[Range[A]])] =
if(order.lteqv(range.start, start)) {
if(order.lt(range.end, start))
some((this, none)) // they are completely to the left of us
else if(order.gteqv(range.end, end))
// we are completely removed
none
else some((Range(enum.succ(range.end), end), none))
} else {
if(order.gt(range.start, end))
some((this, none)) // they are completely to the right of us
else {
val r1 = Range(start, enum.pred(range.start))
val r2: Option[Range[A]] = if(order.lt(range.end, end)) some(Range(enum.succ(range.end), end)) else none
some((r1,r2))
}
}
else {
(order.compare(start, range.start), order.compare(end, range.end)) match {
case (0, x) if x > 0 => (Range.empty(), Range(discrete.succ(range.end), end))
case (0, _) => (Range.empty(), Range.empty())

case (x, y) if x > 0 && y > 0 => (Range.empty(), Range(discrete.succ(range.end), end))
case (x, _) if x > 0 => (Range.empty(), Range.empty())

case (x, y) if x < 0 && y > 0 => (Range(start, discrete.pred(range.start)), Range(discrete.succ(range.end), end))
case (x, _) if x < 0 => (Range(start, discrete.pred(range.start)), Range.empty())
}
}

def +(other: Range[A])(implicit order: Order[A], enum: Enum[A]): (Range[A], Option[Range[A]]) = {
val (l,r) = if(order.lt(this.start,other.start)) (this,other) else (other,this)

if(order.gteqv(l.end, r.start) || enum.adj(l.end, r.start))
(Range(l.start, order.max(l.end,r.end)), none)
else
(Range(l.start, l.end), some(Range(r.start,r.end)))

}

def &(other: Range[A])(implicit order: Order[A]): Option[Range[A]] = {
val start = order.max(this.start, other.start)
val end = order.min(this.end, other.end)
if(order.lteqv(start,end)) Some(Range(start,end)) else None()
}

/**
* Verify that the passed range is a sub-range
*/
def contains(range: Range[A])(implicit order: Order[A]): Boolean =
order.lteqv(start, range.start) && order.gteqv(end, range.end)

/**
* Verify that the passed range is within
* return a stream of the elements in the range
*/
def contains(range: Range[A])(implicit order: Order[A]) =
order.lteqv(start, range.start) && order.gteqv(end, range.end)
def toStreaming(implicit enum: Enum[A], order: Order[A]): Streaming[A] =
order.compare(start,end) match {
case 0 => Streaming(start)
case x if x < 0 => Streaming.cons(start, Streaming.defer(Range(enum.succ(start), end).toStreaming))
case _ => Streaming.cons(start, Streaming.defer(Range(enum.pred(start), end).toStreaming))
}

/**
* Generates the elements of the range [start, end] base of the discrete operations
* Return all the values in the Range as a List
*/
def generate(implicit discrete: Enum[A], order: Order[A]): List[A] = gen (start, end, List.empty)(_=>{})
def toList(implicit enum: Enum[A], order: Order[A]): List[A] = toStreaming.toList

/**
* Returns range [end, start]
Expand All @@ -60,106 +81,34 @@ sealed class Range[A](val start: A, val end: A) {
*/
def contains(x: A)(implicit A: Order[A]) = A.gteqv(x, start) && A.lteqv(x, end)

/**
* Return all the values in the Range as a List
*/
def toList(implicit eA: Enum[A], oA: Order[A]): List[A] = {
val lb = new ListBuilder[A]
foreach{a => val _ = lb += a}
lb.run
}

/**
* Apply function f to each element in range [star, end]
*/
def foreach(f: A => Unit)(implicit discrete: Enum[A], order: Order[A]): Unit = {
val ignore = gen(start, end, List.empty)(f)
}

def map[B](f: A => B)(implicit discrete: Enum[A], order: Order[A]): List[B] =
genMap[B](start, end, List.empty)(f)

def foldLeft[B](s: B, f: (B, A) => B)(implicit discrete: Enum[A], order: Order[A]): B =
generate.foldLeft(s)(f)

private def genMap[B](x: A, y: A, xs: List[B])(f: A => B)(implicit discrete: Enum[A], order: Order[A]): List[B] = {
@tailrec def traverse(a: A, b: A, xs: List[B])(f: A => B)(discrete: Enum[A], order: Order[A]): List[B] = {
if (order.compare(a, b) == 0) {
xs ::: Nel(f(a), List.empty)
} else if (discrete.adj(a, b)) {
xs ::: Nel(f(a), Nel(f(b), List.empty))
} else {
traverse(discrete.succ(a), b, xs ::: (Nel(f(a), List.empty)))(f)(discrete,order)
}
def foreach(f: A => Unit)(implicit enum: Enum[A], order: Order[A]): Unit = {
var i = start
while(order.lteqv(i,end)) {
f(i)
i = enum.succ(i)
}

if(order.lt(x,y))
traverse(x, y, xs)(f)(discrete,order)
else
traverse(x, y, xs)(f)(new Enum[A] {
override def pred(x: A): A = discrete.succ(x)
override def succ(x: A): A = discrete.pred(x)
}, order.reverse)
}

private def gen(x: A, y: A, xs: List[A])(f: A=>Unit)(implicit discrete: Enum[A], order: Order[A]): List[A] = {
@tailrec def traverse(a: A, b: A, xs: List[A])(f: A => Unit)(discrete: Enum[A], order: Order[A]): List[A] = {
if (order.compare(a, b) == 0) {
f(a)
xs ::: Nel(a, List.empty)
} else if (discrete.adj(a, b)) {
f(a)
f(b)
xs ::: Nel(a, Nel(b, List.empty))
}
else {
f(a)
traverse(discrete.succ(a), b, xs ::: (Nel(a, List.empty)))(f)(discrete,order)
}
}
def map[B](f: A => B): Range[B] = Range[B](f(start), f(end))

if(order.lt(x,y))
traverse(x, y, xs)(f)(discrete,order)
else
traverse(x, y, xs)(f)(new Enum[A] {
override def pred(x: A): A = discrete.succ(x)
override def succ(x: A): A = discrete.pred(x)
}, order.reverse)
def foldLeft[B](s: B, f: (B, A) => B)(implicit discrete: Enum[A], order: Order[A]): B = {
var b = s
foreach { a =>
b = f(b,a)
}
b
}

private [dogs] def isEmpty: Boolean = false

def apply(start: A, end: A): Range[A] = Range.apply(start, end)

//override def toString: String = if (isEmpty) s"[]" else s"[$start, $end]"

}

object Range {
def apply[A](x: A, y: A) = new Range[A](x, y)

def empty[A](): Range[A] = EmptyRange()

private [dogs] case object EmptyRange extends Range[Option[Nothing]](None(), None()) {
def apply[A]() = this.asInstanceOf[A]

def unapply[A](r: Range[A]) = r.isEmpty

override def isEmpty = true
}

implicit def rangeShowable[A](implicit s: Show[A]): Show[Range[A]] = new Show[Range[A]] {
override def show(f: Range[A]): Predef.String =
if (f.isEmpty) {
"[]"
}
else {
override def show(f: Range[A]): Predef.String = {
val (a, b) = (s.show(f.start), s.show(f.end))
s"[$a, $b]"
}
}

implicit val emptyShowableRange = new Show[Option[Nothing]] {
override def show(f: Option[Nothing]): Predef.String = ""
}
}
2 changes: 1 addition & 1 deletion core/src/main/scala/dogs/Streaming.scala
Expand Up @@ -706,7 +706,7 @@ object Streaming extends StreamingInstances {
final case class Wait[A](next: Eval[Streaming[A]]) extends Streaming[A]
final case class Cons[A](a: A, tail: Eval[Streaming[A]]) extends Streaming[A]

def unfold[A,B](b: B)(f: B => Option[(A,B)]): Streaming[A] = f(b) match {
def unfold[A,B](b: B)(f: B => Option[(A,B)]): Streaming[A] = f(b) match {
case None() => Streaming.empty
case Some((a,b)) => Streaming.cons(a, defer(unfold(b)(f)))
}
Expand Down
7 changes: 3 additions & 4 deletions core/src/main/scala/dogs/syntax/foldable.scala
Expand Up @@ -2,7 +2,7 @@ package dogs
package syntax

import Predef._
import cats.{Foldable,Order,Semigroup}
import cats.{Eval,Foldable,Order,Semigroup}

trait FoldableSyntax {

Expand Down Expand Up @@ -31,10 +31,9 @@ final class FoldableOps[F[_], A](fa: F[A])(implicit F: Foldable[F]) {
}
}

/* TODO: add this when we get a new cats build
def toStreaming[A](fa: F[A]): Streaming[A] =
def toStreaming(fa: F[A]): Streaming[A] =
F.foldRight(fa, Eval.now(Streaming.empty[A])){ (a, ls) =>
Eval.now(Streaming.cons(a, ls))
}.value
*/

}
32 changes: 9 additions & 23 deletions docs/src/main/tut/range.md
Expand Up @@ -14,7 +14,7 @@ source: "core/src/main/scala/Range.scala"
- `toList`: returns all the values in the `Range` as a `List`.
- `contains(value)`: verifies `value` is within the `Range`.
- `contains(range)`: verifies `range` is within the `Range`.
- `generate`: returns all the values in the `Range [start, end]`
- `toStreaming`: returns all the values in the `Range [start, end]`
- `reverse`: returns the reverted range `Range [end, start]`
- `-(other)`: Calculate the difference with `Range`.
- It returns a tuple with the difference to the right and to the left of `Range`.
Expand All @@ -37,8 +37,7 @@ import dogs._, dogs.Predef._, cats._, cats.implicits._, dogs.syntax.all._, dogs.
val range = Range(1, 10)
range.show

range.generate
range.toList
range.toStreaming.toList
```

We can get the inverted range using the **reverse** functions
Expand Down Expand Up @@ -66,35 +65,22 @@ Asking for a value that is not within Range
range.contains(20)
```

Asking for the difference to another Range
Asking for the difference between two Ranges returns 0, 1, or 2 result ranges

```tut
val range = Range(10, 20)
range.show

val (l, r) = (range - Range(5, 9))
l.show
r.show

val (l, r) = range - Range(30, 40)
l.show
r.show

val (l, r) = range - Range(15, 18)
l.show
r.show

val (l, r) = range - Range (5, 30)
l.show
r.show
Range(10, 20).show
(range - Range(5, 9)).show
(range - Range(30, 40)).show
(range - Range(15, 18)).show
(range - Range (5, 30)).show
```

Creating an **inverted** range

```tut
val range = Range(50, 20)

range.toList
range.toStreaming.toList
```

The reverse of a range should be its inverted range
Expand Down