From 7d896490156b432b94f53a8b53f968a02f8f4bd3 Mon Sep 17 00:00:00 2001 From: Abhijit Sarkar Date: Thu, 4 Jan 2024 05:40:17 -0800 Subject: [PATCH] Complete up to P20 --- README.md | 20 ++++++++++---------- src/main/scala/list/P03.scala | 2 +- src/main/scala/list/P07.scala | 10 ++++++---- src/main/scala/list/P10.scala | 1 + src/main/scala/list/P11.scala | 18 ++++++++++++++++++ src/main/scala/list/P12.scala | 14 ++++++++++++++ src/main/scala/list/P13.scala | 18 ++++++++++++++++++ src/main/scala/list/P14.scala | 11 +++++++++++ src/main/scala/list/P15.scala | 11 +++++++++++ src/main/scala/list/P16.scala | 16 ++++++++++++++++ src/main/scala/list/P17.scala | 16 ++++++++++++++++ src/main/scala/list/P18.scala | 16 ++++++++++++++++ src/main/scala/list/P19.scala | 16 ++++++++++++++++ src/main/scala/list/P20.scala | 16 ++++++++++++++++ src/test/scala/list/P11Suite.scala | 21 +++++++++++++++++++++ src/test/scala/list/P12Suite.scala | 15 +++++++++++++++ src/test/scala/list/P13Suite.scala | 15 +++++++++++++++ src/test/scala/list/P14Suite.scala | 14 ++++++++++++++ src/test/scala/list/P15Suite.scala | 16 ++++++++++++++++ src/test/scala/list/P16Suite.scala | 20 ++++++++++++++++++++ src/test/scala/list/P17Suite.scala | 19 +++++++++++++++++++ src/test/scala/list/P18Suite.scala | 20 ++++++++++++++++++++ src/test/scala/list/P19Suite.scala | 15 +++++++++++++++ src/test/scala/list/P20Suite.scala | 16 ++++++++++++++++ 24 files changed, 341 insertions(+), 15 deletions(-) create mode 100644 src/main/scala/list/P11.scala create mode 100644 src/main/scala/list/P12.scala create mode 100644 src/main/scala/list/P13.scala create mode 100644 src/main/scala/list/P14.scala create mode 100644 src/main/scala/list/P15.scala create mode 100644 src/main/scala/list/P16.scala create mode 100644 src/main/scala/list/P17.scala create mode 100644 src/main/scala/list/P18.scala create mode 100644 src/main/scala/list/P19.scala create mode 100644 src/main/scala/list/P20.scala create mode 100644 src/test/scala/list/P11Suite.scala create mode 100644 src/test/scala/list/P12Suite.scala create mode 100644 src/test/scala/list/P13Suite.scala create mode 100644 src/test/scala/list/P14Suite.scala create mode 100644 src/test/scala/list/P15Suite.scala create mode 100644 src/test/scala/list/P16Suite.scala create mode 100644 src/test/scala/list/P17Suite.scala create mode 100644 src/test/scala/list/P18Suite.scala create mode 100644 src/test/scala/list/P19Suite.scala create mode 100644 src/test/scala/list/P20Suite.scala diff --git a/README.md b/README.md index 4762e63..00c4f59 100644 --- a/README.md +++ b/README.md @@ -24,25 +24,25 @@ [P10](src/main/scala/list/P10.scala) (*) Run-length encoding of a list. -P11 (*) Modified run-length encoding. +[P11](src/main/scala/list/P11.scala) (*) Modified run-length encoding. -P12 (**) Decode a run-length encoded list. +[P12](src/main/scala/list/P12.scala) (**) Decode a run-length encoded list. -P13 (**) Run-length encoding of a list (direct solution). +[P13](src/main/scala/list/P13.scala) (**) Run-length encoding of a list (direct solution). -P14 (*) Duplicate the elements of a list. +[P14](src/main/scala/list/P14.scala) (*) Duplicate the elements of a list. -P15 (**) Duplicate the elements of a list a given number of times. +[P15](src/main/scala/list/P15.scala) (**) Duplicate the elements of a list a given number of times. -P16 (**) Drop every Nth element from a list. +[P16](src/main/scala/list/P16.scala) (**) Drop every Nth element from a list. -P17 (*) Split a list into two parts. +[P17](src/main/scala/list/P17.scala) (*) Split a list into two parts. -P18 (**) Extract a slice from a list. +[P18](src/main/scala/list/P18.scala) (**) Extract a slice from a list. -P19 (**) Rotate a list N places to the left. +[P19](src/main/scala/list/P19.scala) (**) Rotate a list N places to the left. -P20 (*) Remove the Kth element from a list. +[P20](src/main/scala/list/P20.scala) (*) Remove the Kth element from a list. P21 (*) Insert an element at a given position into a list. diff --git a/src/main/scala/list/P03.scala b/src/main/scala/list/P03.scala index aaf7643..82e0b32 100644 --- a/src/main/scala/list/P03.scala +++ b/src/main/scala/list/P03.scala @@ -13,4 +13,4 @@ object P03: def nth[A](n: Int, l: List[A]): A = l match case x :: _ if n == 0 => x case x :: tail => nth(n - 1, tail) - case Nil => throw NoSuchElementException("n larger than list size") + case Nil => throw NoSuchElementException("n too large") diff --git a/src/main/scala/list/P07.scala b/src/main/scala/list/P07.scala index 749a4c3..0ec1224 100644 --- a/src/main/scala/list/P07.scala +++ b/src/main/scala/list/P07.scala @@ -6,9 +6,11 @@ package list // res0: List[Any] = List(1, 1, 2, 3, 5, 8) object P07: - def flatten(l: List[Any]): List[Any] = l.foldRight(Nil)(flatten) + def flatten[A](l: List[A | List[A]]): List[A] = l.foldRight(List.empty[A])(flatten) - private def flatten(x: Any, acc: List[Any]): List[Any] = + private def flatten[A](x: A | List[A], acc: List[A]): List[A] = + // Can't check for instanceOf(List[A]) since + // A vanishes at runtime (type erasure). if x.isInstanceOf[List[Any]] - then x.asInstanceOf[List[Any]].foldRight(acc)(flatten) - else x :: acc + then x.asInstanceOf[List[A]].foldRight(acc)(flatten) + else x.asInstanceOf[A] :: acc diff --git a/src/main/scala/list/P10.scala b/src/main/scala/list/P10.scala index 552da70..468c9de 100644 --- a/src/main/scala/list/P10.scala +++ b/src/main/scala/list/P10.scala @@ -13,4 +13,5 @@ package list object P10: def encode[A](l: List[A]): List[(Int, A)] = + // size takes O(n) time on lists. P09.pack(l).map(xs => (xs.size, xs.head)) diff --git a/src/main/scala/list/P11.scala b/src/main/scala/list/P11.scala new file mode 100644 index 0000000..408cdb0 --- /dev/null +++ b/src/main/scala/list/P11.scala @@ -0,0 +1,18 @@ +package list + +// P11 (*) Modified run-length encoding. +// Modify the result of problem P10 in such a way that if an element has no +// duplicates it is simply copied into the result list. Only elements with +// duplicates are transferred as (N, E) terms. +// +// Example: +// scala> encodeModified(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)) +// res0: List[Any] = List((4,'a), 'b, (2,'c), (2,'a), 'd, (4,'e)) + +object P11: + + def encodeModified[A](l: List[A]): List[A | (Int, A)] = + P09 + .pack(l) + // size takes O(n) time on lists. + .map(xs => if xs.tail.isEmpty then xs.head else (xs.size, xs.head)) diff --git a/src/main/scala/list/P12.scala b/src/main/scala/list/P12.scala new file mode 100644 index 0000000..a569609 --- /dev/null +++ b/src/main/scala/list/P12.scala @@ -0,0 +1,14 @@ +package list + +// P12 (**) Decode a run-length encoded list. +// Given a run-length code list generated as specified in problem P10, +// construct its uncompressed version. +// +// Example: +// scala> decode(List((4, 'a), (1, 'b), (2, 'c), (2, 'a), (1, 'd), (4, 'e))) +// res0: List[Symbol] = List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e) + +object P12: + + def decode[A](l: List[(Int, A)]): List[A] = + l.flatMap((len, x) => List.fill(len)(x)) diff --git a/src/main/scala/list/P13.scala b/src/main/scala/list/P13.scala new file mode 100644 index 0000000..6c68cbe --- /dev/null +++ b/src/main/scala/list/P13.scala @@ -0,0 +1,18 @@ +package list + +// P13 (**) Run-length encoding of a list (direct solution). +// Implement the so-called run-length encoding data compression method +// directly. I.e. don't use other methods you've written (like P09's +// pack); do all the work directly. +// +// Example: +// scala> encodeDirect(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e)) +// res0: List[(Int, Symbol)] = List((4,'a), (1,'b), (2,'c), (2,'a), (1,'d), (4,'e)) + +object P13: + + def encodeDirect[A](l: List[A]): List[(Int, A)] = + l.foldRight(List.empty[(Int, A)]): + case (x, Nil) => List((1, x)) + case (x, acc @ ((_, y) :: _)) if x != y => (1, x) :: acc + case (_, (n, y) :: ys) => (n + 1, y) :: ys diff --git a/src/main/scala/list/P14.scala b/src/main/scala/list/P14.scala new file mode 100644 index 0000000..537d212 --- /dev/null +++ b/src/main/scala/list/P14.scala @@ -0,0 +1,11 @@ +package list + +// P14 (*) Duplicate the elements of a list. +// Example: +// scala> duplicate(List('a, 'b, 'c, 'c, 'd)) +// res0: List[Symbol] = List('a, 'a, 'b, 'b, 'c, 'c, 'c, 'c, 'd, 'd) + +object P14: + + def duplicate[A](l: List[A]): List[A] = + l.flatMap(x => List(x, x)) diff --git a/src/main/scala/list/P15.scala b/src/main/scala/list/P15.scala new file mode 100644 index 0000000..36dcd10 --- /dev/null +++ b/src/main/scala/list/P15.scala @@ -0,0 +1,11 @@ +package list + +// P15 (**) Duplicate the elements of a list a given number of times. +// Example: +// scala> duplicateN(3, List('a, 'b, 'c, 'c, 'd)) +// res0: List[Symbol] = List('a, 'a, 'a, 'b, 'b, 'b, 'c, 'c, 'c, 'c, 'c, 'c, 'd, 'd, 'd) + +object P15: + + def duplicateN[A](n: Int, l: List[A]): List[A] = + l.flatMap(x => List.fill(n)(x)) diff --git a/src/main/scala/list/P16.scala b/src/main/scala/list/P16.scala new file mode 100644 index 0000000..ffdb5a8 --- /dev/null +++ b/src/main/scala/list/P16.scala @@ -0,0 +1,16 @@ +package list + +// P16 (**) Drop every Nth element from a list. +// Example: +// scala> drop(3, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k)) +// res0: List[Symbol] = List('a, 'b, 'd, 'e, 'g, 'h, 'j, 'k) + +object P16: + + def drop[A](n: Int, l: List[A]): List[A] = + // Other ways: + // xs.withFilter(...).map(...) + // for (..) yield + // xs.flatMap(...) + l.zipWithIndex.collect: + case (x, i) if (i + 1) % n != 0 => x diff --git a/src/main/scala/list/P17.scala b/src/main/scala/list/P17.scala new file mode 100644 index 0000000..2b2f667 --- /dev/null +++ b/src/main/scala/list/P17.scala @@ -0,0 +1,16 @@ +package list + +// P17 (*) Split a list into two parts. +// The length of the first part is given. Use a Tuple for your result. +// +// Example: +// scala> split(3, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k)) +// res0: (List[Symbol], List[Symbol]) = (List('a, 'b, 'c),List('d, 'e, 'f, 'g, 'h, 'i, 'j, 'k)) +object P17: + + def split[A](n: Int, l: List[A]): (List[A], List[A]) = l match + case Nil => (Nil, Nil) + case xs if n == 0 => (Nil, xs) + case x :: xs => + split((n - 1), xs) match + case (left, right) => (x :: left, right) diff --git a/src/main/scala/list/P18.scala b/src/main/scala/list/P18.scala new file mode 100644 index 0000000..9ad6ac6 --- /dev/null +++ b/src/main/scala/list/P18.scala @@ -0,0 +1,16 @@ +package list + +// P18 (**) Extract a slice from a list. +// Given two indices, I and K, the slice is the list containing the elements +// from and including the Ith element up to but not including the Kth +// element of the original list. Start counting the elements with 0. +// +// Example: +// scala> slice(3, 7, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k)) +// res0: List[Symbol] = List('d, 'e, 'f, 'g) +object P18: + + def slice[A](start: Int, end: Int, l: List[A]): List[A] = + if end < start + then throw new IllegalArgumentException("invalid bounds") + else P17.split(start, l)(1).take(end - start) diff --git a/src/main/scala/list/P19.scala b/src/main/scala/list/P19.scala new file mode 100644 index 0000000..bf0e515 --- /dev/null +++ b/src/main/scala/list/P19.scala @@ -0,0 +1,16 @@ +package list + +// P19 (**) Rotate a list N places to the left. +// Examples: +// scala> rotate(3, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k)) +// res0: List[Symbol] = List('d, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'a, 'b, 'c) +// +// scala> rotate(-2, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k)) +// res1: List[Symbol] = List('j, 'k, 'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i) + +object P19: + + def rotate[A](n: Int, l: List[A]): List[A] = + val k = Math.floorMod(n, l.size) + val (left, right) = P17.split(k, l) + right ++ left diff --git a/src/main/scala/list/P20.scala b/src/main/scala/list/P20.scala new file mode 100644 index 0000000..39bef31 --- /dev/null +++ b/src/main/scala/list/P20.scala @@ -0,0 +1,16 @@ +package list + +// P20 (*) Remove the Kth element from a list. +// Return the list and the removed element in a Tuple. Elements are +// numbered from 0. +// +// Example: +// scala> removeAt(1, List('a, 'b, 'c, 'd)) +// res0: (List[Symbol], Symbol) = (List('a, 'c, 'd),'b) + +object P20: + + def removeAt[A](n: Int, l: List[A]): (List[A], A) = + P17.split(n, l) match + case (_, Nil) => throw new NoSuchElementException("n too large") + case (left, right) => (left ++ right.tail, right.head) diff --git a/src/test/scala/list/P11Suite.scala b/src/test/scala/list/P11Suite.scala new file mode 100644 index 0000000..2793820 --- /dev/null +++ b/src/test/scala/list/P11Suite.scala @@ -0,0 +1,21 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import org.scalacheck.Gen + +class P11Suite extends ScalaCheckSuite: + + property("modified run-length encoding"): + forAllNoShrink(Gen.listOf(Gen.choose(1, 5))) { (lst: List[Int]) => + val xs = lst.flatMap(n => List.fill(n)(n)) + val obtained = P11.encodeModified(xs) + val expected = xs + .foldRight(List.empty[(Int, Int)])((x, acc) => + if (acc.isEmpty || acc.head(1) != x) + then (1, x) :: acc + else (acc.head(0) + 1, acc.head(1)) :: acc.tail + ) + .map((len, x) => if len == 1 then x else (len, x)) + assertEquals(obtained, expected) + } diff --git a/src/test/scala/list/P12Suite.scala b/src/test/scala/list/P12Suite.scala new file mode 100644 index 0000000..ff29e96 --- /dev/null +++ b/src/test/scala/list/P12Suite.scala @@ -0,0 +1,15 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import org.scalacheck.Gen + +class P12Suite extends ScalaCheckSuite: + + property("decode run-length encoded list"): + forAllNoShrink(Gen.listOf(Gen.choose(1, 5))) { (lst: List[Int]) => + val xs = lst.map(x => (x, x)) + val obtained = P12.decode(xs) + val expected = lst.flatMap(x => List.fill(x)(x)) + assertEquals(obtained, expected) + } diff --git a/src/test/scala/list/P13Suite.scala b/src/test/scala/list/P13Suite.scala new file mode 100644 index 0000000..1a6f220 --- /dev/null +++ b/src/test/scala/list/P13Suite.scala @@ -0,0 +1,15 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import org.scalacheck.Gen + +class P13Suite extends ScalaCheckSuite: + + property("run-length encoding direct"): + forAllNoShrink(Gen.listOf(Gen.choose(1, 5))) { (lst: List[Int]) => + val xs = lst.flatMap(n => List.fill(n)(n)) + val obtained = P13.encodeDirect(xs) + val expected = P10.encode(xs) + assertEquals(obtained, expected) + } diff --git a/src/test/scala/list/P14Suite.scala b/src/test/scala/list/P14Suite.scala new file mode 100644 index 0000000..f91a2e2 --- /dev/null +++ b/src/test/scala/list/P14Suite.scala @@ -0,0 +1,14 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import org.scalacheck.Gen + +class P14Suite extends ScalaCheckSuite: + + property("duplicate the elements"): + forAllNoShrink(Gen.listOf(ListGen.genInt)) { (lst: List[Int]) => + val obtained = P14.duplicate(lst) + val expected = lst.flatMap(n => List.fill(2)(n)) + assertEquals(obtained, expected) + } diff --git a/src/test/scala/list/P15Suite.scala b/src/test/scala/list/P15Suite.scala new file mode 100644 index 0000000..3e6778b --- /dev/null +++ b/src/test/scala/list/P15Suite.scala @@ -0,0 +1,16 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import org.scalacheck.Gen +import scala.util.Random + +class P15Suite extends ScalaCheckSuite: + + property("duplicate the elements N times"): + forAllNoShrink(Gen.listOf(ListGen.genInt)) { (lst: List[Int]) => + val n = Random.between(1, 5) + val obtained = P15.duplicateN(n, lst) + val expected = lst.flatMap(x => List.fill(n)(x)) + assertEquals(obtained, expected) + } diff --git a/src/test/scala/list/P16Suite.scala b/src/test/scala/list/P16Suite.scala new file mode 100644 index 0000000..6f4b1c6 --- /dev/null +++ b/src/test/scala/list/P16Suite.scala @@ -0,0 +1,20 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import org.scalacheck.Gen +import scala.util.Random + +class P16Suite extends ScalaCheckSuite: + + property("drop every Nth element"): + forAllNoShrink(Gen.listOf(ListGen.genInt)) { (lst: List[Int]) => + val n = + if lst.isEmpty + then 0 + else Random.between(1, lst.size + 1) + val obtained = P16.drop(n, lst) + // guard in for-comprehension is sugar for withFilter. + val expected = for ((x, i) <- lst.zipWithIndex if (i + 1) % n != 0) yield x + assertEquals(obtained, expected) + } diff --git a/src/test/scala/list/P17Suite.scala b/src/test/scala/list/P17Suite.scala new file mode 100644 index 0000000..2b3e202 --- /dev/null +++ b/src/test/scala/list/P17Suite.scala @@ -0,0 +1,19 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import org.scalacheck.Gen +import scala.util.Random + +class P17Suite extends ScalaCheckSuite: + + property("split a list into two parts"): + forAllNoShrink(Gen.listOf(ListGen.genInt)) { (lst: List[Int]) => + val n = + if lst.isEmpty + then 0 + else Random.between(1, lst.size + 1) + val obtained = P17.split(n, lst) + val expected = lst.splitAt(n) + assertEquals(obtained, expected) + } diff --git a/src/test/scala/list/P18Suite.scala b/src/test/scala/list/P18Suite.scala new file mode 100644 index 0000000..8efd075 --- /dev/null +++ b/src/test/scala/list/P18Suite.scala @@ -0,0 +1,20 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import org.scalacheck.Gen +import scala.util.Random + +class P18Suite extends ScalaCheckSuite: + + property("extract a slice"): + forAllNoShrink(Gen.listOf(ListGen.genInt)) { (lst: List[Int]) => + val end = + if lst.isEmpty + then 0 + else Random.between(1, lst.size + 1) + val start = if end == 0 then 0 else Random.between(0, end) + val obtained = P18.slice(start, end, lst) + val expected = lst.splitAt(start)._2.take(end - start) + assertEquals(obtained, expected, s"start=${start}, end=${end}") + } diff --git a/src/test/scala/list/P19Suite.scala b/src/test/scala/list/P19Suite.scala new file mode 100644 index 0000000..5349ca2 --- /dev/null +++ b/src/test/scala/list/P19Suite.scala @@ -0,0 +1,15 @@ +package list + +import munit.FunSuite + +class P19Suite extends FunSuite: + + test("rotate a list N places to the left"): + val xs = ('a' to 'k').toList + val obtained1 = P19.rotate(3, xs) + val expected1 = List('d', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'a', 'b', 'c') + assertEquals(obtained1, expected1) + + val obtained2 = P19.rotate(-2, xs) + val expected2 = List('j', 'k', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i') + assertEquals(obtained2, expected2) diff --git a/src/test/scala/list/P20Suite.scala b/src/test/scala/list/P20Suite.scala new file mode 100644 index 0000000..f519501 --- /dev/null +++ b/src/test/scala/list/P20Suite.scala @@ -0,0 +1,16 @@ +package list + +import munit.ScalaCheckSuite +import org.scalacheck.Prop.forAllNoShrink +import scala.util.Random + +class P20Suite extends ScalaCheckSuite: + + property("remove the Kth element"): + forAllNoShrink(ListGen.genNotEmpty) { (lst: List[Int]) => + val k = Random.between(0, lst.size) + val obtained = P20.removeAt(k, lst) + val (left, right) = lst.splitAt(k) + val expected = (left ++ right.tail, right.head) + assertEquals(obtained, expected, s"k=${k}") + }