Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concept/map reduce #595

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions concepts/map/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"blurb": "We provide here a basic explanation about the map higher-order function and its most common usage.",
"authors": ["JaumeAmoresDS"]
}
35 changes: 35 additions & 0 deletions concepts/map/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Introduction

In its most basic form, the higher-order function `map` accepts two arguments: a function `f` and a sequence of elements `s`. It then applies `f` to each element of the sequence, and returns a list where the i-th element is the result of applying `f` to the i-th element of `s`:

```clojure
(map inc [1 2 3]) ; => (2 3 4)
```

The previous example applies function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result of this application as a list `(2 3 4)`, where each element is the result of applying `inc` to the corresponding element of [1 2 3].

Let's see another example where we greet a given person with the message "Welcome":

```clojure
(defn say-welcome
[name]
(str "Welcome " name "!"))

(map say-welcome ["Chris" "Jane" "Peter"]) ; => ("Welcome Chris!" "Welcome Jane!" "Welcome Peter!")
```

## Returning a lazy sequence

Previously we provided a simplified explanation of how `map` operates. In reality, `map` returns a so-called *lazy* sequence. Although an explanation of lazy sequences goes beyond the scope of this introduction, it basically means that the elements of the resulting list are not generated until they are actually needed. In other words, it is not until we actually use elements of this list (e.g., to print them or retrieve their value) that the function `f` passed to `map` is applied to generate each element. This is advantageous when `f` is computationally expensive and we end-up only needing to retrieve some of the elements of the resulting list later in the program.

## Using multiple collections

`map` allows multiple collections to be passed as input. If the number of collections we pass is `n`, the function `f` will receive `n` elements, one for each collection in the list. The result is a list where the i-th element is obtained by applying `f` to the i-th element of each collection:

```clojure
(def coll_a [a1 a2])
(def coll_b [b1 b2])
(def coll_c [c1 c2])
(map f coll_a coll_b coll_c) ; => ((f a1 b1 c1) (f a2 b2 c2))
```

19 changes: 19 additions & 0 deletions concepts/map/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Introduction

In its most basic form, the higher-order function `map` accepts two arguments: a function `f` and a sequence of elements `s`. It then applies `f` to each element of the sequence, and returns a list where the i-th element is the result of applying `f` to the i-th element of `s`:

```clojure
(map inc [1 2 3]) ; => (2 3 4)
```

The previous example applies function `inc`, which increases a number by one, to each element of the vector `[1 2 3]`, and returns the result of this application as a list `(2 3 4)`, where each element is the result of applying `inc` to the corresponding element of [1 2 3].

Let's see another example where we greet a given person with the message "Welcome":

```clojure
(defn say-welcome
[name]
(str "Welcome " name "!"))

(map say-welcome ["Chris" "Jane" "Peter"]) ; => ("Welcome Chris!" "Welcome Jane!" "Welcome Peter!")
```
10 changes: 10 additions & 0 deletions concepts/map/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"url": "https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/map",
"description": "Clojure API: map"
},
{
"url": "https://clojure.org/guides/higher_order_functions",
"description": "Clojure guides: Higher Order Functions"
}
]
4 changes: 4 additions & 0 deletions concepts/reduce/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"blurb": "We provide here a basic explanation about the reduce higher-order function and its most common usage.",
"authors": ["JaumeAmoresDS"]
}
57 changes: 57 additions & 0 deletions concepts/reduce/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Introduction

The higher-order funcion `reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` applies the function `f` to `val` and the first element `x_1` of the collection `coll`: `(f val x_1)`. Then, it applies the function `f` to its own result and the second element `x_2` of the collection `coll`: `(f (f val x_1) x_2)`. Then again, it applies `f` to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using `+` as our function:

```clojure
(reduce + 100 [1 2 3 4])
;=> (+ 100 1)
;=> (+ 101 2)
;=> (+ 103 3)
;=> (+ 106 4)
;=> 110
```

When called with only two arguments, `(reduce f coll)` applies the function `f` to the two first elements `x_1` and `x_2` of the collection `coll`, then applies `f` to its own result and the third element `x_3` of `coll`, then again to its own result and the fourth element of `coll`, and so on until all the elements of `coll` are used:

```clojure
(reduce + [1 2 3 4])
;=> (+ 1 2)
;=> (+ 3 3)
;=> (+ 6 4)
;=> 10
```

In all these cases, the function `f` must accept either two arguments or a variable number of arguments as happens in the previous case with function `+`. Let's see an example with a user-defined function:

```clojure
(defn include-if-even
[coll x]
(if (= (mod x 2) 0)
(conj coll x)
coll))
```

In the previous example, the function `include-if-even` accepts two arguments: a collection `coll` and a number `x`. It then checks if `x` module 2 is 0, in which case `x` is an even number. If that's the case, it includes this number into the collection `coll` and returns this collection. Otherwise, it returns the original collection. We can then apply `reduce` to collect all the even numbers from a given collection as follows:

```clojure
(reduce include-if-even [] [1 2 3 4])
;=> (include-if-even [] 1)
;=> (include-if-even [] 2)
;=> (include-if-even [2] 3)
;=> (include-if-even [2] 4)
;=> [2 4]
```

Note that in the previous example we must necessarily pass three arguments to `reduce`: our function `include-if-even`, an initial collection `[]`, and a collection of numbers like `[1 2 3 4]`. The initial collection `[]` is necessary due to the fact that `include-if-even` needs it as its first argument.

## Especial cases

Especial cases arise when we use an empty collection or a collection with only one item:

- If we apply `reduce` to an empty collection, `(reduce f val coll)` returns `(f val)`, and `(reduce f coll)` returns the result of applying `f` with no arguments. In this case, the function `f` must be able to use no arguments.
- If we apply `reduce` to a collection with only one item, `(reduce f val coll)` returns `(f val x_1)`, i.e., the result of applying `f` to `val` and the first element `x_1` of `coll`. If used with only two arguments, `(f coll)` returns the only element found in `coll`, and `f` is not called.

## Using collections of functions

`reduce` accepts any type of collection, including one that contains functions. In that case, we will typically use three arguments `(reduce f val coll)`, and the function `f` applies the first function `g_1` from the collection to `val`, then the second function `g_2` to the result of the previous application, `(f (f val g_1) g_2)` and so on until all the functions are applied.

45 changes: 45 additions & 0 deletions concepts/reduce/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Introduction

The higher-order funcion `reduce` accepts either two or three arguments. When called with three arguments, `(reduce f val coll)` applies the function `f` to `val` and the first element `x_1` of the collection `coll`: `(f val x_1)`. Then, it applies the function `f` to its own result and the second element `x_2` of the collection `coll`: `(f (f val x_1) x_2)`. Then again, it applies `f` to its previous result and the third element of `coll`, and so on until all the elements of `coll` are used. Let's see a typical example using `+` as our function:

```clojure
(reduce + 100 [1 2 3 4])
;=> (+ 100 1)
;=> (+ 101 2)
;=> (+ 103 3)
;=> (+ 106 4)
;=> 110
```

When called with only two arguments, `(reduce f coll)` applies the function `f` to the two first elements `x_1` and `x_2` of the collection `coll`, then applies `f` to its own result and the third element `x_3` of `coll`, then again to its own result and the fourth element of `coll`, and so on until all the elements of `coll` are used:

```clojure
(reduce + [1 2 3 4])
;=> (+ 1 2)
;=> (+ 3 3)
;=> (+ 6 4)
;=> 10
```

In all these cases, the function `f` must accept either two arguments or a variable number of arguments as happens in the previous case with function `+`. Let's see an example with a user-defined function:

```clojure
(defn include-if-even
[coll x]
(if (= (mod x 2) 0)
(conj coll x)
coll))
```

In the previous example, the function `include-if-even` accepts two arguments: a collection `coll` and a number `x`. It then checks if `x` module 2 is 0, in which case `x` is an even number. If that's the case, it includes this number into the collection `coll` and returns this collection. Otherwise, it returns the original collection. We can then apply `reduce` to collect all the even numbers from a given collection as follows:

```clojure
(reduce include-if-even [] [1 2 3 4])
;=> (include-if-even [] 1)
;=> (include-if-even [] 2)
;=> (include-if-even [2] 3)
;=> (include-if-even [2] 4)
;=> [2 4]
```

Note that in the previous example we must necessarily pass three arguments to `reduce`: our function `include-if-even`, an initial collection `[]`, and a collection of numbers like `[1 2 3 4]`. The initial collection `[]` is necessary due to the fact that `include-if-even` needs it as its first argument.
26 changes: 26 additions & 0 deletions concepts/reduce/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"url": "https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/partial",
"description": "Clojure API: partial"
},
{
"url": "https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/comp",
"description": "Clojure API: comp"
},
{
"url": "https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/comp",
"description": "Clojure API: comp"
},
{
"url": "https://clojuredocs.org/clojure.core/memoize",
"description": "Clojure docs: memoize"
},
{
"url": "https://clojuredocs.org/clojure.core/juxt",
"description": "Clojure docs: juxt"
},
{
"url": "https://clojure.org/guides/higher_order_functions",
"description": "Clojure guides: Higher Order Functions"
}
]
10 changes: 10 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,16 @@
"uuid": "9d9d7cf1-8504-4f7b-b2a0-1b3b638dc0d9",
"slug": "functions-generating-functions",
"name": "Functions generating functions"
},
{
"uuid": "2369e86f-6921-49b6-807d-b1bdf9e37cd2",
"slug": "map",
"name": "map"
},
{
"uuid": "1fcc926b-4e79-4377-874f-41626e59b87b",
"slug": "reduce",
"name": "reduce"
}
],
"key_features": [
Expand Down
Loading