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