Skip to content

Commit 712895a

Browse files
author
Javier de Silóniz Sandino
committed
Rest of exercises regarding lists
1 parent fc9599d commit 712895a

File tree

2 files changed

+296
-21
lines changed

2 files changed

+296
-21
lines changed

src/main/scala/fpinscalalib/FunctionalDataStructuresSection.scala

Lines changed: 266 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fpinscalalib
22

33
import org.scalatest.{FlatSpec, Matchers}
4+
import fpinscalalib.List._
45

56
/** @param name functional_data_structures
67
*/
@@ -88,7 +89,7 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
8889
case Cons(x, Cons(2, Cons(4, _))) => x
8990
case Nil => 42
9091
case Cons(x, Cons(y, Cons(3, Cons(4, _)))) => x + y
91-
case Cons(h, t) => h + List.sum(t)
92+
case Cons(h, t) => h + sum(t)
9293
case _ => 101
9394
}
9495
x shouldBe res0
@@ -111,8 +112,8 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
111112
*/
112113

113114
def listTakeAssert(res0: List[Int], res1: List[Int]) {
114-
List.tail(List(1, 2, 3)) shouldBe res0
115-
List.tail(List(1)) shouldBe res1
115+
tail(List(1, 2, 3)) shouldBe res0
116+
tail(List(1)) shouldBe res1
116117
}
117118

118119
/**
@@ -130,8 +131,8 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
130131
*/
131132

132133
def listSetHeadAssert(res0: List[Int], res1: List[String]) {
133-
List.setHead(List(1, 2, 3), 3) shouldBe res0
134-
List.setHead(List("a", "b"), "c") shouldBe res1
134+
setHead(List(1, 2, 3), 3) shouldBe res0
135+
setHead(List("a", "b"), "c") shouldBe res1
135136
}
136137

137138
/**
@@ -150,11 +151,11 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
150151
*/
151152

152153
def listDropAssert(res0: List[Int], res1: List[Int], res2: List[Int], res3: List[Int], res4: List[Int]) {
153-
List.drop(List(1, 2, 3), 1) shouldBe res0
154-
List.drop(List(1, 2, 3), 0) shouldBe res1
155-
List.drop(List("a", "b"), 2) shouldBe res2
156-
List.drop(List(1, 2), 3) shouldBe res3
157-
List.drop(Nil, 1) shouldBe res4
154+
drop(List(1, 2, 3), 1) shouldBe res0
155+
drop(List(1, 2, 3), 0) shouldBe res1
156+
drop(List("a", "b"), 2) shouldBe res2
157+
drop(List(1, 2), 3) shouldBe res3
158+
drop(Nil, 1) shouldBe res4
158159
}
159160

160161
/**
@@ -173,10 +174,10 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
173174
*/
174175

175176
def listDropWhileAssert(res0: List[Int], res1: List[Int], res2: List[Int], res3: List[Int]) {
176-
List.dropWhile(List(1, 2, 3), (x: Int) => x < 2) shouldBe res0
177-
List.dropWhile(List(1, 2, 3), (x: Int) => x > 2) shouldBe res1
178-
List.dropWhile(List(1, 2, 3), (x: Int) => x > 0) shouldBe res2
179-
List.dropWhile(Nil, (x: Int) => x > 0) shouldBe res3
177+
dropWhile(List(1, 2, 3), (x: Int) => x < 2) shouldBe res0
178+
dropWhile(List(1, 2, 3), (x: Int) => x > 2) shouldBe res1
179+
dropWhile(List(1, 2, 3), (x: Int) => x > 0) shouldBe res2
180+
dropWhile(Nil, (x: Int) => x > 0) shouldBe res3
180181
}
181182

182183
/**
@@ -196,8 +197,8 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
196197
*/
197198

198199
def listInitAssert(res0: List[Int], res1: List[Int]) {
199-
List.init(List(1, 2, 3)) shouldBe res0
200-
List.init(List(1)) shouldBe res1
200+
init(List(1, 2, 3)) shouldBe res0
201+
init(List(1)) shouldBe res1
201202
}
202203

203204
/**
@@ -250,10 +251,10 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
250251
*/
251252

252253
def listFoldRightSumAssert(res0: Int,res1: Int, res2: Int, res3: Int, res4: Int, res5: Int, res6: List[Int], res7: Int, res8: Int, res9: Int, res10: Int) {
253-
List.foldRight(Cons(1, Cons(2, Cons(3, Nil))), 0)((x,y) => x + y) shouldBe 6
254-
res0 + List.foldRight(Cons(2, Cons(3, Nil)), 0)((x,y) => x + y) shouldBe 6
255-
res1 + res2 + List.foldRight(Cons(3, Nil), 0)((x,y) => x + y) shouldBe 6
256-
res3 + res4 + res5 + List.foldRight(res6, 0)((x,y) => x + y) shouldBe 6
254+
foldRight(Cons(1, Cons(2, Cons(3, Nil))), 0)((x,y) => x + y) shouldBe 6
255+
res0 + foldRight(Cons(2, Cons(3, Nil)), 0)((x,y) => x + y) shouldBe 6
256+
res1 + res2 + foldRight(Cons(3, Nil), 0)((x,y) => x + y) shouldBe 6
257+
res3 + res4 + res5 + foldRight(res6, 0)((x,y) => x + y) shouldBe 6
257258
res7 + res8 + res9 + res10 shouldBe 6
258259
}
259260

@@ -263,7 +264,7 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
263264
*/
264265

265266
def listFoldRightNilConsAssert(res0: List[Int]) {
266-
List.foldRight(List(1, 2, 3), Nil:List[Int])(Cons(_, _)) shouldBe res0
267+
foldRight(List(1, 2, 3), Nil:List[Int])(Cons(_, _)) shouldBe res0
267268
}
268269

269270
/**
@@ -276,5 +277,249 @@ object FunctionalDataStructuresSection extends FlatSpec with Matchers with org.s
276277

277278
length(l) shouldBe 5
278279
}
280+
281+
/**
282+
* Our implementation of `foldRight` is not tail-recursive and will result in a `StackOverflowError` for large lists
283+
* (we say it's not `stack-safe`). Let's write another general list-recursion function, `foldLeft`, that is
284+
* tail-recursive, using the techniques we discussed in the previous chapter:
285+
*
286+
* {{{
287+
* def foldLeft[A,B](l: List[A], z: B)(f: (B, A) => B): B =
288+
* l match {
289+
* case Nil => z
290+
* case Cons(h,t) => foldLeft(t, f(z,h))(f)
291+
* }
292+
* }}}
293+
*
294+
* Let's write functions `sum`, `product` and `length` of a list using `foldLeft`:
295+
*/
296+
297+
def listFoldLeftSumProductLengthAssert(res0: Int, res1: Double, res2: Int, res3: Int): Unit = {
298+
def sum3(l: List[Int]) = foldLeft(l, res0)(_ + _)
299+
def product3(l: List[Double]) = foldLeft(l, res1)(_ * _)
300+
def length2[A](l: List[A]): Int = foldLeft(l, res2)((acc,h) => acc + res3)
301+
302+
def listInts = List(1, 2, 3, 4, 5)
303+
def listDoubles = List(1.0, 2.0, 3.0)
304+
sum3(listInts) shouldBe 15
305+
product3(listDoubles) shouldBe 6.0
306+
length2(listInts) shouldBe 5
307+
}
308+
309+
/**
310+
* As we saw above, we can write the previous functions we implemented using `foldRight` with `foldLeft`. Let's continue
311+
* with `reverse`:
312+
*
313+
* {{{
314+
* def reverse[A](l: List[A]): List[A] = foldLeft(l, List[A]())((acc, h) => Cons(h, acc))
315+
* }}}
316+
*
317+
* In fact, we can write `foldLeft` in terms of `foldRight`, and the other way around:
318+
*
319+
* {{{ 
320+
* def foldRightViaFoldLeft[A,B](l: List[A], z: B)(f: (A,B) => B): B =
321+
* foldLeft(reverse(l), z)((b,a) => f(a,b))
322+
*
323+
* def foldLeftViaFoldRight[A,B](l: List[A], z: B)(f: (B,A) => B): B =
324+
* foldRight(l, (b:B) => b)((a,g) => b => g(f(b,a)))(z)
325+
* }}}
326+
*
327+
* The implementation of `foldRight` in terms of `reverse` and `foldLeft` is a common trick for avoiding stack overflows
328+
* when implementing a strict `foldRight` function as we've done in this chapter. Note that the other implementation is
329+
* more of theoretical interest - it isn't stack-safe and won't work for large lists.
330+
*
331+
*/
332+
333+
/**
334+
* Another function we can implement by using `foldRight` is `append`:
335+
*
336+
* {{{
337+
* def appendViaFoldRight[A](l: List[A], r: List[A]): List[A] =
338+
* foldRight(l, r)(Cons(_,_))
339+
* }}}
340+
*
341+
* Take a look at its implementation and check how it works:
342+
*/
343+
344+
def listAppendAssert(res0: List[Int], res1: List[Int], res2: List[Int], res3: List[Int]): Unit = {
345+
append(List(1, 2, 3), List(1, 2)) shouldBe res0
346+
append(List(1, 2, 3), Nil) shouldBe res1
347+
append(Nil, List(1, 2)) shouldBe res2
348+
append(Nil, Nil) shouldBe res3
349+
}
350+
351+
/**
352+
* `foldRight` can also be useful to write a function `concat` that concatenates a list of lists into a single list.
353+
* Take a look at its implementation:
354+
*
355+
* {{{
356+
* def concat[A](l: List[List[A]]): List[A] =
357+
* foldRight(l, Nil:List[A])(append)
358+
* }}}
359+
*
360+
* Since `append` takes time proportional to its first argument, and this first argument never grows because of the
361+
* right-associativity of `foldRight`, this function is linear in the total length of all lists.
362+
*
363+
*/
364+
365+
/**
366+
* Let's keep digging into the uses of `foldLeft` and `foldRight`, by implementing a function that transforms a list
367+
* of integers by adding 1 to each element:
368+
*/
369+
370+
def listAdd1Assert(res0: Int): Unit = {
371+
def add1(l: List[Int]): List[Int] = foldRight(l, Nil : List[Int])((h, t) => Cons(h + res0,t))
372+
add1(List(1, 2, 3)) shouldBe List(2, 3, 4)
373+
}
374+
375+
/**
376+
* We can do something similar to turn each value in a List[Double] into a String:
377+
*
378+
* {{{
379+
* def doubleToString(l: List[Double]): List[String] =
380+
* foldRight(l, Nil:List[String])((h,t) => Cons(h.toString,t))
381+
* }}}
382+
*/
383+
384+
/**
385+
* Both `add1` and `doubleToString` modify each element in a list while maintaining its structure. We can generalize
386+
* it in the following way:
387+
*
388+
* {{{
389+
* def map[A,B](l: List[A])(f: A => B): List[B] =
390+
* foldRight(l, Nil:List[B])((h,t) => Cons(f(h),t))
391+
* }}}
392+
*
393+
* You may notice that we are using `foldRight` to implement `map`, even though it's not stack-safe. We can also
394+
* use `foldRightViaFoldLeft` (that relies on reversing the original list), or use local mutation. Take a look at both
395+
* alternatives:
396+
*
397+
* {{{
398+
* def map_1[A,B](l: List[A])(f: A => B): List[B] =
399+
* foldRightViaFoldLeft(l, Nil:List[B])((h,t) => Cons(f(h),t))
400+
*
401+
* def map_2[A,B](l: List[A])(f: A => B): List[B] = {
402+
* val buf = new collection.mutable.ListBuffer[B]
403+
* def go(l: List[A]): Unit = l match {
404+
* case Nil => ()
405+
* case Cons(h,t) => buf += f(h); go(t)
406+
* }
407+
* go(l)
408+
* List(buf.toList: _*) // converting from the standard Scala list to the list we've defined here
409+
* }
410+
* }}}
411+
*/
412+
413+
/**
414+
* Let's apply the same principle as in `map` to remove elements from a list, starting with a function to remove all
415+
* odd numbers from a List[Int]:
416+
*/
417+
def listRemoveOdds(res0: Int, res1: Int): Unit = {
418+
def removeOdds(l: List[Int]): List[Int] =
419+
foldRight(l, Nil:List[Int])((h, t) => if (h % res0 == res1) Cons(h, t) else t)
420+
removeOdds(List(1, 2, 3, 4, 5)) shouldBe List(2, 4)
421+
}
422+
423+
/**
424+
* Following the same principle, let's generalize the function above to be able to remove elements from a list unless
425+
* they satisfy a given predicate:
426+
*
427+
* {{{
428+
* def filter[A](l: List[A])(f: A => Boolean): List[A] =
429+
* foldRight(l, Nil:List[A])((h,t) => if (f(h)) Cons(h,t) else t)
430+
* }}}
431+
*
432+
* The same considerations regarding the different choices of implementations as in `map` apply to `filter`. The one
433+
* above isn't stack-safe, so we should make a choice between using `foldRightViaFoldLeft` instead of `foldRight`, or
434+
* perform local mutations.
435+
*/
436+
437+
/**
438+
* We're going to implement a new function that works like `map` except that the function given will return a list
439+
* instead of a single result, and that list should be inserted into the final resulting list:
440+
*
441+
* {{{
442+
* def flatMap[A,B](l: List[A])(f: A => List[B]): List[B] =
443+
* concat(map(l)(f))
444+
* }}}
445+
*
446+
* Let's try it out:
447+
*/
448+
449+
def listFlatMapAssert(res0: List[Int]): Unit = {
450+
flatMap(List(1, 2, 3))(i => List(i, i)) shouldBe res0
451+
}
452+
453+
/**
454+
* We can also implement `filter` using `flatMap`:
455+
*
456+
* {{{
457+
* def filterViaFlatMap[A](l: List[A])(f: A => Boolean): List[A] =
458+
* flatMap(l)(a => if (f(a)) List(a) else Nil)
459+
* }}}
460+
*
461+
*/
462+
463+
/**
464+
* Now we're going to write a function that accepts two lists of integers and constructs a new list by adding
465+
* corresponding elements. For example, `List(1, 2, 3)` and `List(4, 5, 6)` become `List(5, 7, 9)`:
466+
*
467+
* {{{
468+
* def addPairwise(a: List[Int], b: List[Int]): List[Int] = (a,b) match {
469+
* case (Nil, _) => Nil
470+
* case (_, Nil) => Nil
471+
* case (Cons(h1,t1), Cons(h2,t2)) => Cons(h1+h2, addPairwise(t1,t2))
472+
* }
473+
* }}}
474+
*
475+
* We can generalize the function above so that it's not specific to integers or addition, `zipWith`:
476+
*
477+
* {{{
478+
* def zipWith[A,B,C](a: List[A], b: List[B])(f: (A,B) => C): List[C] = (a,b) match {
479+
* case (Nil, _) => Nil
480+
* case (_, Nil) => Nil
481+
* case (Cons(h1,t1), Cons(h2,t2)) => Cons(f(h1,h2), zipWith(t1,t2)(f))
482+
* }
483+
* }}}
484+
*
485+
* Let's try out `zipWith` in the following exercise:
486+
*/
487+
488+
def listZipWithAssert(res0: List[String], res1: List[String]): Unit = {
489+
zipWith(List("a", "b", "c"), List("A", "B", "C"))(_ + _) shouldBe res0
490+
zipWith(List(1, 2, 3), List(4, 5, 6))(_.toString + _.toString()) shouldBe res1
491+
}
492+
493+
/**
494+
* As a final example to work with lists, let's implement a `hasSubsequence` function for checking whether a `List`
495+
* contains another `List` as a subsequence. For instance, `List(1, 2, 3, 4)` would have `List(1, 2)`, `List(2, 3)`
496+
* and `List(4)` as subsequences, among others:
497+
*
498+
* {{{
499+
* @annotation.tailrec
500+
* def startsWith[A](l: List[A], prefix: List[A]): Boolean = (l,prefix) match {
501+
* case (_,Nil) => true
502+
* case (Cons(h,t),Cons(h2,t2)) if h == h2 => startsWith(t, t2)
503+
* case _ => false
504+
* }
505+
*
506+
* @annotation.tailrec
507+
* def hasSubsequence[A](sup: List[A], sub: List[A]): Boolean = sup match {
508+
* case Nil => sub == Nil
509+
* case _ if startsWith(sup, sub) => true
510+
* case Cons(h,t) => hasSubsequence(t, sub)
511+
* }
512+
* }}}
513+
*
514+
* Take a deep look at the implementation of this function, and then try it out in the next exercise:
515+
*/
516+
517+
def listHasSubsequenceAssert(res0: Boolean, res1: Boolean, res2: Boolean): Unit = {
518+
def l = List(1, 2, 3, 4, 5)
519+
520+
hasSubsequence(l, List(2, 3)) shouldBe res0
521+
hasSubsequence(l, List(0, 1)) shouldBe res1
522+
hasSubsequence(l, Nil) shouldBe res2
523+
}
279524
}
280525

src/test/scala/fpinscalalib/FunctionalDataStructuresSpec.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,34 @@ class FunctionalDataStructuresSpec extends Spec with Checkers {
5252
def `list length with foldRight asserts` = {
5353
check(Test.testSuccess(FunctionalDataStructuresSection.listLengthAssert _, 0 :: 1 :: HNil))
5454
}
55+
56+
def `list foldLeft sum product length asserts` = {
57+
check(Test.testSuccess(FunctionalDataStructuresSection.listFoldLeftSumProductLengthAssert _, 0 :: 1.0 :: 0 :: 1 :: HNil))
58+
}
59+
60+
def `list foldLeft append asserts` = {
61+
check(Test.testSuccess(FunctionalDataStructuresSection.listAppendAssert _,
62+
List(1, 2, 3, 1, 2) :: List(1, 2, 3) :: List(1, 2) :: List[Int]() :: HNil))
63+
}
64+
65+
def `list add 1 asserts` = {
66+
check(Test.testSuccess(FunctionalDataStructuresSection.listAdd1Assert _, 1 :: HNil))
67+
}
68+
69+
def `list remove odds asserts` = {
70+
check(Test.testSuccess(FunctionalDataStructuresSection.listRemoveOdds _, 2 :: 0 :: HNil))
71+
}
72+
73+
def `list flatMap asserts` = {
74+
check(Test.testSuccess(FunctionalDataStructuresSection.listFlatMapAssert _, List(1, 1, 2, 2, 3, 3) :: HNil))
75+
}
76+
77+
def `list zipWith asserts` = {
78+
check(Test.testSuccess(FunctionalDataStructuresSection.listZipWithAssert _,
79+
List("aA", "bB", "cC") :: List("14", "25", "36") :: HNil))
80+
}
81+
82+
def `list hasSubsequence asserts` = {
83+
check(Test.testSuccess(FunctionalDataStructuresSection.listHasSubsequenceAssert _, true :: false :: true :: HNil))
84+
}
5585
}

0 commit comments

Comments
 (0)