Skip to content

Commit 355576e

Browse files
authored
add approaches and article to difference-of-squares (#160)
* add approaches and article to difference-of-squares * text adjustments in the article * add eol to article
1 parent 615ce71 commit 355576e

File tree

11 files changed

+568
-0
lines changed

11 files changed

+568
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"introduction": {
3+
"authors": ["oxe-i"],
4+
"contributors": []
5+
},
6+
"approaches": [
7+
{
8+
"uuid": "854dac53-adfd-46ad-8c64-46e401a46a37",
9+
"slug": "recurse-numbers",
10+
"title": "Recurse Over Numbers",
11+
"blurb": "recurse over all numbers to sum them up.",
12+
"authors": [
13+
"oxe-i"
14+
]
15+
},
16+
{
17+
"uuid": "3e4413e6-89cb-4e1e-8e83-89e84754dd0f",
18+
"slug": "mathematical-formula",
19+
"title": "Mathematical Formula",
20+
"blurb": "calculate sums using known mathematical formulas.",
21+
"authors": [
22+
"oxe-i"
23+
]
24+
},
25+
{
26+
"uuid": "0e48332a-1b9b-4322-b941-933d1c718d67",
27+
"slug": "folding-over-range",
28+
"title": "Folding Over Range",
29+
"blurb": "use a fold to sum all numbers in a range.",
30+
"authors": [
31+
"oxe-i"
32+
]
33+
}
34+
]
35+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Folding Over Range
2+
3+
In functional programming languages, a [fold][fold] is a common way to iterate over a collection while accumulating a result.
4+
5+
A fold processes the elements of a collection one by one, updating an accumulator at each step.
6+
To perform a fold, three things are needed:
7+
8+
1. A collection of elements, often stored in a `List` or an `Array`.
9+
2. An initial value for the accumulator.
10+
3. A function that combines the current element with the accumulator to produce a new accumulator.
11+
12+
For example, if we fold a collection of numbers using `0` as the initial value and addition as the combining function, the result will be the sum of all elements in the collection.
13+
14+
Lean provides helper functions for `List` and `Array` that generate collections of sequential numbers, which can then be processed with a fold:
15+
16+
```lean
17+
def squareOfSum (number : Nat) : Nat :=
18+
let sum := (List.range' 1 number).foldl (fun acc x => acc + x) 0
19+
sum ^ 2
20+
21+
def sumOfSquares (number : Nat) : Nat :=
22+
(List.range' 1 number).foldl (init := 0) fun acc x => acc + x^2
23+
24+
def differenceOfSquares (number : Nat) : Nat := (squareOfSum number) - (sumOfSquares number)
25+
```
26+
27+
Both [List.range'][list-range'] and [Array.range'][array-range'] create collections of consecutive natural numbers.
28+
They take:
29+
30+
- a starting value (the first argument, `1` in the example), and
31+
- a count (the second argument, `number`).
32+
33+
The result is a collection containing `number` values starting from the initial value.
34+
35+
An optional third argument specifies the _step size_, which determines how much each value increases relative to the previous one.
36+
If omitted, the default step is `1`, producing consecutive numbers.
37+
38+
Lean also defines a [fold directly for `Nat`][nat-fold].
39+
Instead of folding over an explicit collection, this function iterates from `0` up to (but not including) a given number.
40+
41+
The folding function receives three arguments:
42+
43+
1. the current index,
44+
2. a proof that the index is less than the bound, and
45+
3. the current accumulator.
46+
47+
```lean
48+
def squareOfSum (number : Nat) : Nat :=
49+
let sum := number.fold (init := 0) fun i _ acc => (i + 1) + acc
50+
sum ^ 2
51+
52+
def sumOfSquares (number : Nat) : Nat :=
53+
number.fold (fun i _ acc => acc + (i + 1) ^ 2) 0
54+
55+
def differenceOfSquares (number : Nat) : Nat := (squareOfSum number) - (sumOfSquares number)
56+
```
57+
58+
This approach avoids explicitly creating a collection and instead performs the iteration directly over the natural numbers, which can be more efficient.
59+
60+
[fold]: https://en.wikipedia.org/wiki/Fold_(higher-order_function)
61+
[list-range']: https://lean-lang.org/doc/reference/latest/Basic-Types/Linked-Lists/#List___range___
62+
[array-range']: https://lean-lang.org/doc/reference/latest/Basic-Types/Arrays/#Array___range___
63+
[nat-fold]: https://lean-lang.org/doc/reference/latest/Basic-Types/Natural-Numbers/#Nat___fold
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def squareOfSum (number : Nat) : Nat :=
2+
let sum := number.fold (init := 0) fun i _ acc => (i + 1) + acc
3+
sum ^ 2
4+
5+
def sumOfSquares (number : Nat) : Nat :=
6+
number.fold (fun i _ acc => acc + (i + 1) ^ 2) 0
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Introduction
2+
3+
There are multiple ways to solve `difference-of-squares`.
4+
Some of them include:
5+
6+
* Recursing over numbers.
7+
* Using known mathematical formulas.
8+
* Folding over a range of numbers.
9+
10+
## Approach: Recursing over numbers
11+
12+
Recurse over decreasing numbers until a base case is reached, accumulating the result:
13+
14+
```lean
15+
def sumN (n : Nat) (acc : Nat) : Nat :=
16+
if n = 0
17+
then acc
18+
else sumN (n - 1) (acc + n)
19+
20+
def squareOfSum (number : Nat) : Nat :=
21+
(sumN number 0) ^ 2
22+
23+
def sumSquareN (n : Nat) (acc : Nat) : Nat :=
24+
if n = 0
25+
then acc
26+
else sumSquareN (n - 1) (acc + (n ^ 2))
27+
28+
def sumOfSquares (number : Nat) : Nat :=
29+
sumSquareN number 0
30+
31+
def differenceOfSquares (number : Nat) : Nat := (squareOfSum number) - (sumOfSquares number)
32+
```
33+
34+
For more details, see the [recurse-numbers][recurse-numbers] approach.
35+
36+
## Approach: Using mathematical formulas
37+
38+
Compute the results directly using mathematical formulas:
39+
40+
```lean
41+
def sumFirstNPositive (n : Nat) : Nat :=
42+
n * (n + 1) / 2
43+
44+
def squareOfSum (number : Nat) : Nat :=
45+
(sumFirstNPositive number) ^ 2
46+
47+
def sumOfSquares (number : Nat) : Nat :=
48+
(number * (number + 1) * (2 * number + 1)) / 6
49+
50+
def differenceOfSquares (number : Nat) : Nat :=
51+
(squareOfSum number) - (sumOfSquares number)
52+
```
53+
54+
For more details, see the [mathematical-formula][mathematical-formula] approach.
55+
56+
## Approach: Folding over range
57+
58+
Use a `fold` to iterate over a range of values and accumulate the result:
59+
60+
```lean
61+
def squareOfSum (number : Nat) : Nat :=
62+
let sum := number.fold (init := 0) fun i _ acc => (i + 1) + acc
63+
sum ^ 2
64+
65+
def sumOfSquares (number : Nat) : Nat :=
66+
number.fold (fun i _ acc => acc + (i + 1) ^ 2) 0
67+
68+
def differenceOfSquares (number : Nat) : Nat := (squareOfSum number) - (sumOfSquares number)
69+
```
70+
71+
For more details, see the [folding-over-range][folding-over-range] approach.
72+
73+
[recurse-numbers]: https://exercism.org/tracks/lean/exercises/difference-of-squares/approaches/recurse-numbers
74+
[mathematical-formula]: https://exercism.org/tracks/lean/exercises/difference-of-squares/approaches/mathematical-formula
75+
[folding-over-range]: https://exercism.org/tracks/lean/exercises/difference-of-squares/approaches/folding-over-range
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Mathematical Formula
2+
3+
A possible way to solve this exercise is to use [known mathematical formulas][formulas]:
4+
5+
* The sum of positive integers up to `n`, inclusive, is `n * (n + 1) / 2`.
6+
Squaring that sum gives `squareOfSum`.
7+
* The sum of the squares of positive integers up to `n`, inclusive, is `n * (n + 1) * (2 * n + 1) / 6`.
8+
9+
```lean
10+
def sumFirstNPositive (n : Nat) : Nat :=
11+
n * (n + 1) / 2
12+
13+
def squareOfSum (number : Nat) : Nat :=
14+
(sumFirstNPositive number) ^ 2
15+
16+
def sumOfSquares (number : Nat) : Nat :=
17+
(number * (number + 1) * (2 * number + 1)) / 6
18+
19+
def differenceOfSquares (number : Nat) : Nat :=
20+
(squareOfSum number) - (sumOfSquares number)
21+
```
22+
23+
This is probably the most efficient approach, since the problem reduces to a few constant multiplications and divisions, whereas the other approaches are `O(n)`.
24+
On the other hand, the solution becomes more opaque because these formulas are not obvious from the structure of the problem.
25+
They require previous knowledge or further research.
26+
27+
[formulas]: https://en.wikipedia.org/wiki/Triangular_number
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def squareOfSum (number : Nat) : Nat :=
2+
(number * (number + 1) / 2) ^ 2
3+
4+
def sumOfSquares (number : Nat) : Nat :=
5+
(number * (number + 1) * (2 * number + 1)) / 6
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Recurse All
2+
3+
In functional languages like Lean, recursion often fills the same role as loops in other languages.
4+
It is the primary tool used to iterate over a group of elements or a range of values.
5+
6+
This exercise can be solved using straightforward recursion:
7+
8+
```lean
9+
def sumFirstNPositive (n : Nat) : Nat :=
10+
match n with
11+
| 0 => 0
12+
| x + 1 => (x + 1) + sumFirstNPositive x
13+
14+
def squareOfSum (number : Nat) : Nat :=
15+
(sumFirstNPositive number) ^ 2
16+
17+
def sumOfSquares (number : Nat) : Nat :=
18+
match number with
19+
| 0 => 0
20+
| x + 1 => (x + 1) * (x + 1) + sumOfSquares x
21+
22+
def differenceOfSquares (number : Nat) : Nat :=
23+
(squareOfSum number) - (sumOfSquares number)
24+
```
25+
26+
In cases like this, where a function is defined as a simple pattern match on a value, there is an equivalent and more concise notation:
27+
28+
```lean
29+
def sumFirstNPositive : Nat -> Nat
30+
| 0 => 0
31+
| x + 1 => (x + 1) + sumFirstNPositive x
32+
33+
def squareOfSum (number : Nat) : Nat :=
34+
(sumFirstNPositive number) ^ 2
35+
36+
def sumOfSquares : Nat -> Nat
37+
| 0 => 0
38+
| x + 1 => (x + 1) * (x + 1) + sumOfSquares x
39+
40+
def differenceOfSquares (number : Nat) : Nat :=
41+
(squareOfSum number) - (sumOfSquares number)
42+
```
43+
44+
It is also possible to use `if...then...else` to achieve the same effect:
45+
46+
```lean
47+
def sumN (n : Nat) :=
48+
if n = 0 then 0 else n + sumN (n - 1)
49+
50+
def squareOfSum (number : Nat) : Nat :=
51+
(sumN number) ^ 2
52+
53+
def sumOfSquares (number : Nat) : Nat :=
54+
if number = 0 then 0 else number ^ 2 + sumOfSquares (number - 1)
55+
56+
def differenceOfSquares (number : Nat) : Nat := (squareOfSum number) - (sumOfSquares number)
57+
```
58+
59+
Both functions can also be made [tail-recursive][tail-call] by adding an extra accumulating parameter:
60+
61+
```lean
62+
def sumN (n : Nat) (acc : Nat) : Nat :=
63+
if n = 0 then acc else sumN (n - 1) (acc + n)
64+
65+
def squareOfSum (number : Nat) : Nat :=
66+
(sumN number 0) ^ 2
67+
68+
def sumSquareN (n : Nat) (acc : Nat) : Nat :=
69+
if n = 0 then acc else sumSquareN (n - 1) (acc + (n ^ 2))
70+
71+
def sumOfSquares (number : Nat) : Nat :=
72+
sumSquareN number 0
73+
74+
def differenceOfSquares (number : Nat) : Nat := (squareOfSum number) - (sumOfSquares number)
75+
```
76+
77+
A tail-recursive function may be optimized by the compiler so that it does not require additional stack space, making it as efficient as a loop.
78+
79+
This approach has the advantage of being clear and self-documenting.
80+
The problem involves summing a range of numbers, and that is exactly what the code expresses.
81+
82+
However, it is also inefficient, since it requires as many recursive calls as `number`, the argument to the functions.
83+
Even a tail-recursive variant still requires iterating over all values from `0` up to `number`.
84+
85+
[tail-call]: https://en.wikipedia.org/wiki/Tail_call
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def sumN (n : Nat) (acc : Nat) : Nat :=
2+
if n = 0 then acc else sumN (n - 1) (acc + n)
3+
4+
def squareOfSum (number : Nat) : Nat :=
5+
(sumN number 0) ^ 2
6+
7+
def sumOfSquares (number : Nat) (acc := 0) : Nat :=
8+
if n = 0 then acc else sumSquareN (n - 1) (acc + (n ^ 2))
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"articles": [
3+
{
4+
"uuid": "14152e49-e077-4a8e-a1c2-5812ebb5bbdd",
5+
"slug": "formula-is-equivalent-to-recursion",
6+
"title": "Proving that the mathematical formula and the recursive approaches are equivalent",
7+
"blurb": "proving that the mathematical formula and the recursive approaches are equivalent.",
8+
"authors": [
9+
"oxe-i"
10+
]
11+
}
12+
]
13+
}

0 commit comments

Comments
 (0)