From 12b82aa9c15bdee1a4c2821b91bfb376826db702 Mon Sep 17 00:00:00 2001 From: milesfrain <milesfrain@users.noreply.github.com> Date: Mon, 6 Sep 2021 14:43:41 -0700 Subject: [PATCH] Improve naming, tests, and solutions of factorization-related exercises and examples (#387) * Ch4 rename factorize to primeFactors, improve solution and tests * Ch4 improve factors tests * Ch4 rename fact to factorial * Ch4 rename factTailRec to factorialTailRec * Ch4 test more primeFactors corner cases --- exercises/chapter4/test/Examples.purs | 22 +++---- exercises/chapter4/test/Main.purs | 59 ++++++++++++------- .../chapter4/test/no-peeking/Solutions.purs | 22 +++---- text/chapter4.md | 12 ++-- 4 files changed, 64 insertions(+), 51 deletions(-) diff --git a/exercises/chapter4/test/Examples.purs b/exercises/chapter4/test/Examples.purs index 4bbedd835..b2f37864d 100644 --- a/exercises/chapter4/test/Examples.purs +++ b/exercises/chapter4/test/Examples.purs @@ -7,14 +7,14 @@ import Data.Foldable (product) import Data.Maybe (fromMaybe) import Data.Path (Path, ls) --- ANCHOR: fact -fact :: Int -> Int -fact n = +-- ANCHOR: factorial +factorial :: Int -> Int +factorial n = if n == 0 then 1 else - n * fact (n - 1) --- ANCHOR_END: fact + n * factorial (n - 1) +-- ANCHOR_END: factorial -- ANCHOR: fib fib :: Int -> Int @@ -61,13 +61,13 @@ factorsV3 n = do pure [ i, j ] -- ANCHOR_END: factorsV3 --- ANCHOR: factTailRec -factTailRec :: Int -> Int -> Int -factTailRec n acc = +-- ANCHOR: factorialTailRec +factorialTailRec :: Int -> Int -> Int +factorialTailRec n acc = if n == 0 then acc - else factTailRec (n - 1) (acc * n) --- ANCHOR_END: factTailRec + else factorialTailRec (n - 1) (acc * n) +-- ANCHOR_END: factorialTailRec -- ANCHOR: lengthTailRec lengthTailRec :: forall a. Array a -> Int @@ -75,7 +75,7 @@ lengthTailRec arr = length' arr 0 where length' :: Array a -> Int -> Int length' arr' acc = - if null arr' + if null arr' then acc else length' (fromMaybe [] $ tail arr') (acc + 1) -- ANCHOR_END: lengthTailRec diff --git a/exercises/chapter4/test/Main.purs b/exercises/chapter4/test/Main.purs index ec8913bc0..d88471fbc 100644 --- a/exercises/chapter4/test/Main.purs +++ b/exercises/chapter4/test/Main.purs @@ -5,9 +5,10 @@ import Test.Examples import Test.MySolutions import Test.NoPeeking.Solutions -- This line should have been automatically deleted by resetSolutions.sh. See Chapter 2 for instructions. import Data.Array (sort) +import Data.Foldable (sequence_) import Data.Maybe (Maybe(..)) import Data.Path (Path(..), filename, root) -import Data.Tuple (fst) +import Data.Tuple.Nested ((/\)) import Effect (Effect) import Test.Unit (TestSuite, suite, test) import Test.Unit.Assert (assert, assertFalse) @@ -122,13 +123,21 @@ This line should have been automatically deleted by resetSolutions.sh. See Chapt Assert.equal (sort [ [ 3, 4, 5 ], [ 5, 12, 13 ], [ 6, 8, 10 ] ]) $ sort $ triples 13 - suite "Exercise - factorize" do - test "Test small non-prime number" do - Assert.equal [ 3, 2 ] - $ factorize 6 - test "Test number that uses the prime numbers less than 10" do - Assert.equal [ 7, 5, 3, 2 ] - $ factorize 210 + suite "Exercise - primeFactors" do + let + primeFactorsTest :: Int -> Array Int -> _ + primeFactorsTest n xs = + test (show n) do + Assert.equal (sort xs) + $ sort + $ primeFactors n + primeFactorsTest 1 [] + primeFactorsTest 2 [2] + primeFactorsTest 3 [3] + primeFactorsTest 4 [2, 2] + primeFactorsTest 6 [3, 2] + primeFactorsTest 18 [3, 3, 2] + primeFactorsTest 210 [ 7, 5, 3, 2 ] suite "Exercise Group - Folds and Tail Recursion" do test "Exercise - allTrue" do assert "all elements true" @@ -198,27 +207,35 @@ This line should have been automatically deleted by resetSolutions.sh. See Chapt runChapterExamples :: TestSuite runChapterExamples = suite "Chapter Examples" do - test "fact" do + test "factorial" do Assert.equal 120 - $ fact 5 + $ factorial 5 test "fib" do Assert.equal 34 $ fib 9 test "length" do Assert.equal 3 $ length [ 0, 0, 0 ] - test "factors" do - Assert.equal [ [ 1, 10 ], [ 2, 5 ] ] - $ factors 10 - test "factorsV2" do - Assert.equal [ [ 1, 10 ], [ 2, 5 ] ] - $ factorsV2 10 - test "factorsV3" do - Assert.equal [ [ 1, 10 ], [ 2, 5 ] ] - $ factorsV3 10 - test "factTailRec" do + sequence_ do + name /\ f <- + [ "factors" /\ factors + , "factorsV2" /\ factorsV2 + , "factorsV3" /\ factorsV3 + ] + n /\ xs <- + [ 1 /\ [[1,1]] + , 2 /\ [[1,2]] + , 3 /\ [[1,3]] + , 4 /\ [[1,4],[2,2]] + , 10 /\ [[1,10],[2,5]] + , 100 /\ [[1,100],[2,50],[4,25],[5,20],[10,10]] + ] + pure $ test (name <> " " <> show n) do + Assert.equal (sort $ map sort xs) + $ sort $ map sort f n + test "factorialTailRec" do Assert.equal 120 - $ factTailRec 5 1 + $ factorialTailRec 5 1 test "lengthTailRec" do Assert.equal 3 $ lengthTailRec [ 0, 0, 0 ] diff --git a/exercises/chapter4/test/no-peeking/Solutions.purs b/exercises/chapter4/test/no-peeking/Solutions.purs index f06fc225b..945906c46 100644 --- a/exercises/chapter4/test/no-peeking/Solutions.purs +++ b/exercises/chapter4/test/no-peeking/Solutions.purs @@ -57,20 +57,16 @@ triples n = do pure [ i, j, k ] -- | Provide the prime numbers that, multiplied together, make the argument. -factorize :: Int -> Array Int -factorize n = factorize' 2 n [] +primeFactors :: Int -> Array Int +primeFactors n = factorize 2 n where - factorize' :: Int -> Int -> Array Int -> Array Int - factorize' _ 1 result = result - - factorize' divisor dividend result = - let - remainder = rem dividend divisor - in - if remainder == 0 then - factorize' (divisor) (quot dividend divisor) (cons divisor result) - else - factorize' (divisor + 1) dividend result + factorize :: Int -> Int -> Array Int + factorize _ 1 = [] + factorize divisor dividend = + if dividend `mod` divisor == 0 then + cons divisor $ factorize (divisor) (dividend / divisor) + else + factorize (divisor + 1) dividend allTrue :: Array Boolean -> Boolean allTrue bools = foldl (\acc bool -> acc && bool) true bools diff --git a/text/chapter4.md b/text/chapter4.md index b66b0af57..9b842fb0b 100644 --- a/text/chapter4.md +++ b/text/chapter4.md @@ -31,7 +31,7 @@ Let's see some simple examples of recursion in PureScript. Here is the usual _factorial function_ example: ```haskell -{{#include ../exercises/chapter4/test/Examples.purs:fact}} +{{#include ../exercises/chapter4/test/Examples.purs:factorial}} ``` Here, we can see how the factorial function is computed by reducing the problem to a subproblem - that of computing the factorial of a smaller integer. When we reach zero, the answer is immediate. @@ -44,7 +44,7 @@ Here is another common example, which computes the _Fibonacci function_: Again, this problem is solved by considering the solutions to subproblems. In this case, there are two subproblems, corresponding to the expressions `fib (n - 1)` and `fib (n - 2)`. When these two subproblems are solved, we assemble the result by adding the partial results. -Note that, while the above examples of `fact` and `fib` work as intended, a more idiomatic implementation would use pattern matching instead of `if`/`then`/`else`. Pattern matching techniques are discussed in a later chapter. +Note that, while the above examples of `factorial` and `fib` work as intended, a more idiomatic implementation would use pattern matching instead of `if`/`then`/`else`. Pattern matching techniques are discussed in a later chapter. ## Recursion on Arrays @@ -365,7 +365,7 @@ This means that if the guard fails, then the current branch of the array compreh 1. (Easy) Write a function `isPrime` which tests if its integer argument is prime or not. _Hint_: Use the `factors` function. 1. (Medium) Write a function `cartesianProduct` which uses do notation to find the _cartesian product_ of two arrays, i.e. the set of all pairs of elements `a`, `b`, where `a` is an element of the first array, and `b` is an element of the second. 1. (Medium) Write a function `triples :: Int -> Array (Array Int)` which takes a number `n` and returns all Pythagorean triples whose components (the `a`, `b` and `c` values) are each less than or equal to `n`. A _Pythagorean triple_ is an array of numbers `[a, b, c]` such that `a² + b² = c²`. _Hint_: Use the `guard` function in an array comprehension. - 1. (Difficult) Write a function `factorize` which produces the [prime factorization](https://www.mathsisfun.com/prime-factorization.html) of `n`, i.e. the array of prime integers whose product is `n`. _Hint_: for an integer greater than 1, break the problem down into two subproblems: finding the first factor, and finding the remaining factors. + 1. (Difficult) Write a function `primeFactors` which produces the [prime factorization](https://www.mathsisfun.com/prime-factorization.html) of `n`, i.e. the array of prime integers whose product is `n`. _Hint_: for an integer greater than 1, break the problem down into two subproblems: finding the first factor, and finding the remaining factors. ## Folds @@ -441,7 +441,7 @@ It is easy to verify this problem, with the following code in PSCi: ```text > :paste -… f n = +… f n = … if n == 0 … then 0 … else 1 + f (n - 1) @@ -467,10 +467,10 @@ In practice, the PureScript compiler does not replace the recursive call with a Here is an example of a recursive function with all recursive calls in tail position: ```haskell -{{#include ../exercises/chapter4/test/Examples.purs:factTailRec}} +{{#include ../exercises/chapter4/test/Examples.purs:factorialTailRec}} ``` -Notice that the recursive call to `factTailRec` is the last thing that happens in this function - it is in tail position. +Notice that the recursive call to `factorialTailRec` is the last thing that happens in this function - it is in tail position. ## Accumulators