mirror of
https://github.com/Brandon-Rozek/website.git
synced 2024-11-29 15:57:17 -05:00
156 lines
No EOL
3.6 KiB
Markdown
156 lines
No EOL
3.6 KiB
Markdown
---
|
|
date: 2022-11-09 17:45:26-05:00
|
|
draft: false
|
|
math: true
|
|
medium_enabled: true
|
|
medium_post_id: 13bdb8690a4c
|
|
tags:
|
|
- Scala
|
|
- Functional Programming
|
|
title: Different Views of Fold and Their Combinations
|
|
---
|
|
|
|
Fold is a functional programming pattern that operates over some sequence with a binary operation and a starting value. There are two variants:
|
|
|
|
FoldLeft: Performs the binary operation from the left of the sequence to the right.
|
|
|
|
```scala
|
|
List(1, 2, 3, 4).foldLeft(0)(_ + _)
|
|
// ((((0 + 1) + 2) + 3) + 4)
|
|
// 10
|
|
```
|
|
|
|
FoldRight: Performs the binary operation from the right of the sequence to the left.
|
|
|
|
```scala
|
|
List(1, 2, 3, 4).foldRight(0)(_ + _)
|
|
// (1 + (2 + (3 + (4 + 0))))
|
|
// 10
|
|
```
|
|
|
|
The two fold variants only return the same solution if the function being applied is associative. Which is the case for integer addition above.
|
|
|
|
## Definition and the Two Views
|
|
|
|
Definition of `foldLeft` within Scala:
|
|
|
|
```scala
|
|
def foldLeft[B](z: B)(op: (B, A) => B): B = this match {
|
|
case seq: IndexedSeq[A @unchecked] => foldl(seq, 0, z, op)
|
|
case _ =>
|
|
var result = z
|
|
val it = iterator
|
|
while (it.hasNext) {
|
|
result = op(result, it.next())
|
|
}
|
|
result
|
|
}
|
|
|
|
def foldl[B](seq: IndexedSeq[A], start: Int, z: B, op: (B, A) => B): B = {
|
|
@tailrec def loop(at: Int, end: Int, acc: B): B =
|
|
if (at == end) acc
|
|
else loop(at + 1, end, op(acc, seq(at)))
|
|
loop(start, seq.length, z)
|
|
}
|
|
```
|
|
|
|
Notice that this includes both a recursive and iterative definition within one function! Since most people start off by learning loops, let's focus on the iterative implementation.
|
|
|
|
```scala
|
|
var result = z
|
|
while (it.hasNext) {
|
|
result = op(result, it.next())
|
|
}
|
|
```
|
|
|
|
## Combining Folds
|
|
|
|
When building out complex functions we may want to loop multiple times:
|
|
|
|
- While loops next to each other ($n_1 + n_2$ complexity)
|
|
- While loop within a while loop ($n_1 * n_2$ complexity)
|
|
|
|
### $n_1 + n_2$ Complexity
|
|
|
|
The following is an example[^1] of this class of algorithms.
|
|
|
|
Given a list of words, title case each word and combine them into a single sentence.
|
|
|
|
```scala
|
|
val l1 = List("steve", "is", "doing", "great")
|
|
val title = (s: String) => s.substring(0, 1).toUpperCase + s.substring(1)
|
|
|
|
val it1 = l1.iterator
|
|
var id1 = List.empty[String]
|
|
|
|
while (it1.hasNext) {
|
|
id1 = id1 :+ title(it1.next())
|
|
}
|
|
|
|
val it2 = id1.iterator
|
|
var id2 = ""
|
|
while (it2.hasNext) {
|
|
id2 = id2 + " " + it2.next()
|
|
}
|
|
```
|
|
|
|
Converting to fold solution:
|
|
|
|
```scala
|
|
val l1 = List("steve", "is", "doing", "great")
|
|
val title = (s: String) => s.substring(0, 1).toUpperCase + s.substring(1)
|
|
val id1 = List.empty[String]
|
|
val id2 = ""
|
|
|
|
l1.foldLeft(id1)((c, n) =>
|
|
c :+ title(n)
|
|
).foldLeft(id2)((c, n) =>
|
|
c + " " + n
|
|
)
|
|
```
|
|
|
|
### $n_1 * n_2$ Complexity
|
|
|
|
An example of this class of programs is the [Cartesian product](https://en.wikipedia.org/wiki/Cartesian_product)
|
|
$$
|
|
(1, 2, 3) \times (4, 5,6) \rightarrow ((1,4), (1,5), (1,6), \dots, (3,4), (3,5), (3,6))
|
|
$$
|
|
Iterative Implementation:
|
|
|
|
```scala
|
|
val listInput1 = List(1,2,3)
|
|
val listInput2 = List(4,5,6)
|
|
|
|
var id_outer = List.empty[(Int, Int)]
|
|
val it1 = listInput1.iterator
|
|
|
|
while (it1.hasNext) {
|
|
var id_inner = List.empty[(Int, Int)]
|
|
val n1 = it1.next()
|
|
val it2 = listInput2.iterator
|
|
while (it2.hasNext) {
|
|
val n2 = it2.next()
|
|
result_inner = result_inner :+ (n1, n2)
|
|
}
|
|
result = result ++ result_inner
|
|
}
|
|
```
|
|
|
|
Converting to fold solution:
|
|
|
|
```scala
|
|
val l1 = List(1,2,3)
|
|
val l2 = List(4,5,6)
|
|
val id_outer = List.empty[(Int, Int)]
|
|
val id_inner = List.empty[(Int, Int)]
|
|
|
|
l1.foldLeft(id_outer)((c1, n1) =>
|
|
c1 ++ l2.foldLeft(id_inner)((c2, n2) =>
|
|
c2 :+ (n1, n2)
|
|
)
|
|
)
|
|
```
|
|
|
|
|
|
|
|
[^1]: Thanks Clare for thinking of this example! |