website/content/blog/different-views-of-fold.md

156 lines
3.6 KiB
Markdown
Raw Permalink Normal View History

2022-11-09 17:52:25 -05:00
---
2023-01-25 13:20:29 -05:00
date: 2022-11-09 17:45:26-05:00
2022-11-09 17:52:25 -05:00
draft: false
math: true
2023-01-05 14:04:45 -05:00
medium_enabled: true
2023-01-25 13:20:29 -05:00
medium_post_id: 13bdb8690a4c
tags:
- Scala
- Functional Programming
title: Different Views of Fold and Their Combinations
2022-11-09 17:52:25 -05:00
---
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
2022-11-09 18:00:58 -05:00
The following is an example[^1] of this class of algorithms.
2022-11-09 17:52:25 -05:00
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)
)
)
```
2022-11-09 18:00:58 -05:00
2023-01-25 13:20:29 -05:00
[^1]: Thanks Clare for thinking of this example!