Skip to content

Commit

Permalink
Merge pull request #74 from stew/range-opt
Browse files Browse the repository at this point in the history
Refactor Range and Diet to generate values lazily
  • Loading branch information
stew committed Dec 30, 2016
2 parents f7ae076 + 93ccd74 commit 12ce452
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 428 deletions.
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

0 comments on commit 12ce452

Please sign in to comment.