Skip to content

Commit e2ef0aa

Browse files
author
Javier de Silóniz Sandino
committed
Final exercises for chapter 5
1 parent d482144 commit e2ef0aa

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

src/main/scala/fpinscalalib/StrictnessAndLazinessSection.scala

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,4 +328,211 @@ object StrictnessAndLazinessSection extends FlatSpec with Matchers with org.scal
328328
step7 shouldBe step8
329329
step8 shouldBe finalStep
330330
}
331+
332+
/**
333+
* = Infinite streams and corecursion =
334+
*
335+
* Because they’re incremental, the functions we’ve written also work for `infinite streams`. Here’s an example of
336+
* an infinite `Stream` of `1`s:
337+
*
338+
* {{{
339+
* val ones: Stream[Int] = Stream.cons(1, ones)
340+
* }}}
341+
*
342+
* Although ones is infinite, the functions we’ve written so far only inspect the portion of the stream needed to
343+
* generate the demanded output. For example:
344+
*/
345+
346+
def streamOnesAssert(res0: List[Int], res1: Boolean, res2: Boolean, res3: Boolean): Unit = {
347+
ones.take(5).toList shouldBe res0
348+
ones.exists(_ % 2 != 0) shouldBe res1
349+
ones.map(_ + 1).exists(_ % 2 == 0) shouldBe res2
350+
ones.forAll(_ != 1) shouldBe res3
351+
}
352+
353+
/**
354+
* Let's generalize `ones` slightly to the function `constant`, which returns an infinite `Stream` of a given value:
355+
*
356+
* {{{
357+
* def constant[A](a: A): Stream[A] = {
358+
* lazy val tail: Stream[A] = Cons(() => a, () => tail)
359+
* tail
360+
* }
361+
* }}}
362+
*
363+
* Of course, we can generate `number series` with `Stream`s. For example, let's write a function that generates an
364+
* infinite stream of integers (n, n + 1, n + 2...):
365+
*/
366+
367+
def streamIntegersAssert(res0: Int): Unit = {
368+
def from(n: Int): Stream[Int] =
369+
cons(n, from(n + res0))
370+
371+
from(100).take(5).toList shouldBe List(100, 101, 102, 103, 104)
372+
}
373+
374+
/**
375+
* We can also create a function `fibs` that generates the infinite stream of Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8...
376+
*/
377+
378+
def streamFibsAssert(res0: Int, res1: Int): Unit = {
379+
val fibs = {
380+
def go(f0: Int, f1: Int): Stream[Int] =
381+
cons(f0, go(f1, f0+f1))
382+
go(res0, res1)
383+
}
384+
385+
fibs.take(7).toList shouldBe List(0, 1, 1, 2, 3, 5, 8)
386+
}
387+
388+
/**
389+
* Now we're going to write a more general stream-building function called `unfold`. It takes an initial state, and
390+
* a function for producing both the next state and the next value in the generated stream:
391+
*
392+
* {{{
393+
* def unfold[A, S](z: S)(f: S => Option[(A, S)]): Stream[A] = f(z) match {
394+
* case Some((h,s)) => cons(h, unfold(s)(f))
395+
* case None => empty
396+
* }
397+
* }}}
398+
*
399+
* `Option` is used to indicate when the `Stream` should be terminated, if at all. Now that we have `unfold`, let's
400+
* re-write our previous generator functions based on it, starting from `fibs`:
401+
*/
402+
403+
def streamFibsViaUnfoldAssert(res0: Int, res1: Int): Unit = {
404+
val fibsViaUnfold =
405+
unfold((res0, res1)) { case (f0,f1) => Some((f0, (f1, f0+f1))) }
406+
407+
fibsViaUnfold.take(7).toList shouldBe List(0, 1, 1, 2, 3, 5, 8)
408+
}
409+
410+
/**
411+
* `from` follows a similar principle, albeit a little bit more simple:
412+
*/
413+
414+
def streamFromViaUnfoldAssert(res0: Int): Unit = {
415+
def fromViaUnfold(n: Int) = unfold(n)(n => Some((n, n + res0)))
416+
417+
fromViaUnfold(100).take(5).toList shouldBe List(100, 101, 102, 103, 104)
418+
}
419+
420+
/**
421+
* Again, `constant` can be implemented in terms of `unfold` in a very similar way:
422+
*
423+
* {{{
424+
* def constantViaUnfold[A](a: A) = unfold(a)(_ => Some((a,a)))
425+
* }}}
426+
*
427+
* Follow the same pattern to implement `ones` using `unfold`:
428+
*/
429+
430+
def streamOnesViaUnfoldAssert(res0: Some[(Int, Int)]): Unit = {
431+
val onesViaUnfold = unfold(1)(_ => res0)
432+
433+
onesViaUnfold.take(10).toList shouldBe List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
434+
}
435+
436+
/**
437+
* Now we're going to re-implement some of the higher-order functions for `Stream`s, starting with map:
438+
*
439+
* {{{
440+
* def mapViaUnfold[B](f: A => B): Stream[B] = unfold(this) {
441+
* case Cons(h,t) => Some((f(h()), t()))
442+
* case _ => None
443+
* }
444+
* }}}
445+
*
446+
* `take` can also be re-written via `unfold`, let's try it:
447+
*/
448+
449+
def streamTakeViaUnfold(res0: Int, res1: Int): Unit = {
450+
def takeViaUnfold[A](s: Stream[A], n: Int): Stream[A] =
451+
unfold((s, n)) {
452+
case (Cons(h,t), 1) => Some((h(), (Stream.empty, res0)))
453+
case (Cons(h,t), n) if n > 1 => Some((h(), (t(), n - 1)))
454+
case _ => None
455+
}
456+
457+
takeViaUnfold(Stream(1, 2, 3, 4, 5), 5).toList shouldBe List(1, 2, 3, 4, 5)
458+
}
459+
460+
/**
461+
* Let's continue by re-writting `takeWhile` in terms of `unfold`:
462+
*
463+
* {{{
464+
* def takeWhileViaUnfold(f: A => Boolean): Stream[A] = unfold(this) {
465+
* case Cons(h,t) if f(h()) => Some((h(), t()))
466+
* case _ => None
467+
* }
468+
* }}}
469+
*
470+
* We can also bring back functions we saw with `List`s, as `zipWith`:
471+
*
472+
* {{{
473+
* def zipWith[B,C](s2: Stream[B])(f: (A,B) => C): Stream[C] = unfold((this, s2)) {
474+
* case (Cons(h1,t1), Cons(h2,t2)) =>
475+
* Some((f(h1(), h2()), (t1(), t2())))
476+
* case _ => None
477+
* }
478+
* }}}
479+
*
480+
* `zipAll` can also be implemented using `unfold`. Note that it should continue the traversal as long as either
481+
* stream has more elements - it uses `Option` to indicate whether each stream has been exhausted:
482+
*
483+
* {{{
484+
* def zipAll[B](s2: Stream[B]): Stream[(Option[A],Option[B])] = zipWithAll(s2)((_,_))
485+
*
486+
* def zipWithAll[B, C](s2: Stream[B])(f: (Option[A], Option[B]) => C): Stream[C] =
487+
* Stream.unfold((this, s2)) {
488+
* case (Empty, Empty) => None
489+
* case (Cons(h, t), Empty) => Some(f(Some(h()), Option.empty[B]) -> (t(), empty[B]))
490+
* case (Empty, Cons(h, t)) => Some(f(Option.empty[A], Some(h())) -> (empty[A] -> t()))
491+
* case (Cons(h1, t1), Cons(h2, t2)) => Some(f(Some(h1()), Some(h2())) -> (t1() -> t2()))
492+
* }
493+
* }}}
494+
*
495+
* Now we're going to try to implement `startsWith` using the functions we've seen so far:
496+
*
497+
* {{{
498+
* def startsWith[A](s: Stream[A]): Boolean =
499+
* zipAll(s).takeWhile(!_._2.isEmpty) forAll {
500+
* case (h,h2) => h == h2
501+
* }
502+
* }}}
503+
*
504+
* We can also write `tails` using `unfold`. For a given Stream, `tails` returns the `Stream` of suffixes of the input
505+
* sequence, starting with the original `Stream`. For example, given `Stream(1,2,3)`, it would return
506+
* `Stream(Stream(1,2,3), Stream(2,3), Stream(3), Stream())`.
507+
*/
508+
509+
def streamTailsAssert(res0: Int): Unit = {
510+
def tails[A](s: Stream[A]): Stream[Stream[A]] =
511+
unfold(s) {
512+
case Empty => None
513+
case s1 => Some((s1, s1 drop res0))
514+
} append Stream(Stream.empty)
515+
516+
tails(Stream(1, 2, 3)).toList.map(_.toList) shouldBe List(List(1, 2, 3), List(2, 3), List(3), List())
517+
}
518+
519+
/**
520+
* We can generalize tails to the function scanRight, which is like a `foldRight` that returns a stream of the
521+
* intermediate results:
522+
*
523+
* {{{
524+
* def scanRight[B](z: B)(f: (A, => B) => B): Stream[B] =
525+
* foldRight((z, Stream(z)))((a, p0) => {
526+
* lazy val p1 = p0
527+
* val b2 = f(a, p1._1)
528+
* (b2, cons(b2, p1._2))
529+
* })._2
530+
* }}}
531+
*
532+
* For example:
533+
*/
534+
535+
def streamScanRightAssert(res0: List[Int]): Unit = {
536+
Stream(1, 2, 3).scanRight(0)(_ + _).toList shouldBe res0
537+
}
331538
}

src/test/scala/fpinscalalib/StrictnessAndLazinessSpec.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,41 @@ class StrictnessAndLazinessSpec extends Spec with Checkers {
4747
check(Test.testSuccess(StrictnessAndLazinessSection.streamTraceAssert _,
4848
11 :: Stream(2, 3, 4) :: Stream(3, 4) :: 13 :: Stream(4) :: 14 :: HNil))
4949
}
50+
51+
def `stream ones asserts` = {
52+
check(Test.testSuccess(StrictnessAndLazinessSection.streamOnesAssert _,
53+
List(1, 1, 1, 1, 1) :: true :: true :: false :: HNil))
54+
}
55+
56+
def `stream integers asserts` = {
57+
check(Test.testSuccess(StrictnessAndLazinessSection.streamIntegersAssert _, 1 :: HNil))
58+
}
59+
60+
def `stream fibs asserts` = {
61+
check(Test.testSuccess(StrictnessAndLazinessSection.streamFibsAssert _, 0 :: 1 :: HNil))
62+
}
63+
64+
def `stream fibs via unfold asserts` = {
65+
check(Test.testSuccess(StrictnessAndLazinessSection.streamFibsViaUnfoldAssert _, 0 :: 1 :: HNil))
66+
}
67+
68+
def `stream integers via unfold asserts` = {
69+
check(Test.testSuccess(StrictnessAndLazinessSection.streamIntegersAssert _, 1 :: HNil))
70+
}
71+
72+
def `stream ones via unfold asserts` = {
73+
StrictnessAndLazinessSection.streamOnesViaUnfoldAssert(Some(1, 1))
74+
}
75+
76+
def `stream take via unfold asserts` = {
77+
StrictnessAndLazinessSection.streamTakeViaUnfold(0, 1)
78+
}
79+
80+
def `stream tails asserts` = {
81+
StrictnessAndLazinessSection.streamTailsAssert(1)
82+
}
83+
84+
def `stream scanRight asserts` = {
85+
check(Test.testSuccess(StrictnessAndLazinessSection.streamScanRightAssert _, List(6, 5, 3, 0) :: HNil))
86+
}
5087
}

0 commit comments

Comments
 (0)