# ✓ Functional Sequences Transformations¶ ## Sequeces in Clojure¶

Sequences are everywhere in Clojure. It’s a powerful abstract over many different data structures and programming scenarios.

### Lists and vectors are sequences¶

'(:a :b :c)

(:a :b :c)

[1 2 3]

[1 2 3]


### A few functions to process sequences¶

(let [my-list '(:a :b :c)]
(first my-list))

:a

(let [my-list '(:a :b :c)]
(last my-list))

:c

(let [my-vec [1 2 3]]
(first my-vec))

1

(let [my-vec [1 2 3]]
(last my-vec))

3

(let [my-vec [1 2 3]]
(take 2 my-vec))

(1 2)


### Hash-maps are sequences too¶

Hash-maps can also be used as a sequence. It’s a sequence of [key value] pairs. So, each element is a vector of length two consisting of the key and the value.

(let [my-hashmap {:a 1
:b 2
:c 3}]
(first my-hashmap))

[:a 1]

(let [my-hashmap {:a 1
:b 2
:c 3}]
(last my-hashmap))

[:c 3]

(let [my-hashmap {:a 1
:b 2
:c 3}]
(take 2 my-hashmap))

([:a 1] [:b 2])


## Review of for-iteration¶

The for-form always evaluates to a sequence. The for-form generates the elements by iterating over one or more input sequences.

### General form¶

(for [ <symbol> <sequence>
<symbol> <sequence>
...
:when <condition>
:while <condition>
:let [binding...]]
<expression>)

(for [x [:a :b :c]
y [1 2 3]]
[x y])

([:a 1] [:a 2] [:a 3] [:b 1] [:b 2] [:b 3] [:c 1] [:c 2] [:c 3])


### Why use for-form?¶

The for-form is similar to the more traditional for-loops in Python, and thus are easier to read for most people.

### Why not use the for-form?¶

For-form is not friendly to composition.

Consider the scenario of:

1. The first for-form generates a sequence.

2. Then a second for-form process the elements from the first for-form

(defn format-elements [x y]
(str "x=" x " and y=" y))

#'user/format-elements

(for [elem (for [x [:a :b :c]
y [1  2]]
[x y])]
(apply format-elements elem))

("x=:a and y=1" "x=:a and y=2" "x=:b and y=1" "x=:b and y=2" "x=:c and y=1" "x=:c and y=2")

(for [string (for [elem (for [x [:a :b :c]
y [1 2]]
[x y])]
(apply format-elements elem))]
(clojure.string/upper-case string))

("X=:A AND Y=1" "X=:A AND Y=2" "X=:B AND Y=1" "X=:B AND Y=2" "X=:C AND Y=1" "X=:C AND Y=2")


### Functional programming¶

Functions are composable.

Using higher order functions, we have interesting ways to describe sequence transformations.

## Map¶

• map is not a form. It’s is a function.

• The signature of the map function is:

Note

(map <function> <sequence>)
(map <function> <sequence-1> <sequence-2>)
(map <function> <sequence-1> <sequence-2> <sequence-3>)

• The function is called the mapping function, or sometimes the mapper.

• The map function returns a (lazy) sequence.

### Map action¶

The elements from input sequences are used as input arguments to the mapping function. Elements from the input sequences are matched up until one of the sequence is exhausted.

The return values of the mapping function form the output sequence.

(let [xs [1 2 3]
ys [4 5 6 7 8]]
(map + xs ys))

(5 7 9)

(let [xs [1 2 3]
ys [4 5 6]]
(map vector xs ys))

([1 4] [2 5] [3 6])


### Revisiting iterations¶

Note: map is not equivalent to for-form when applied to multiple sequences.

(for [x [:a :b :c]
y [1 2]]
[x y])

([:a 1] [:a 2] [:b 1] [:b 2] [:c 1] [:c 2])

(let [xs [:a :b :c]
ys [1 2]]
(map vector xs ys))

([:a 1] [:b 2])


### Map is composable¶

(map format-elements [:a :b :c] [1 2])

("x=:a and y=1" "x=:b and y=2")

(map clojure.string/upper-case
(map format-elements [:a :b :c] [1 2]))

("X=:A AND Y=1" "X=:B AND Y=2")


(->> (map format-elements [:a :b :c] [1 2])
(map clojure.string/upper-case))

("X=:A AND Y=1" "X=:B AND Y=2")


## Filter¶

Note

(filter <predicate> <sequence>)

• Recall a predicate is a function that returns true or false.

• The filter function returns a sequence from the input sequence that evaluate to true by the predicate function.

### Example¶

(filter #(< 4 (count %))
["hello"
"world"
"again"
"and"
"again"])

("hello" "world" "again" "again")


### Filter is composable¶

(filter #(clojure.string/includes? % "a")
["hello"
"world"
"again"
"and"
"again"])

("again" "and" "again")

(filter #(< 4 (count %))
(filter #(clojure.string/includes? % "a")
["hello"
"world"
"again"
"and"
"again"]))

("again" "again")


(->> ["hello"
"world"
"again"
"and"
"again"]
(filter #(< 4 (count %)))
(filter #(clojure.string/includes? % "a")))

("again" "again")


## Reduce¶

Reduce is the most powerful form of functional sequence processing.

Note

(reduce <reducer-fn> <initial-value> <sequence>)


where the reducer-fn has the signature of:

(<reducer-fn> <state-value> <input-element>)


Unlike map and filter, reduce can produce data value of any type.

It uses a function, known as the reducer, to iteratively transform a state value with the elements in the input sequence.

### Reduce action¶

During the reduce iteration, we starts with a state value being the initial-value.

For each element in the sequence, a new state value is computed as:

(<reducer-fn> state-value element)


### Reducer explained¶

Suppose we have an input sequence:

["hello" "world" "again" "and" "again"]


We may want to compute the total characters.

• Use an integer as the state value, with initial value set to 0.

• Use a reducer function to compute the new state value for each input element.

• The total characters can be computed using reduce.

(fn [total string] (+ total (count string)))

#function[user/eval5902/fn--5903]

(reduce (fn [total string] (+ total (count string)))
0
["hello" "world" "again" "and" "again"])

23


### Another example of reduce¶

Adding a sequence of numbers using reduce can be done using + as the reducer function.

(reduce + 0 (range 1000000))

499999500000


### Composability of map/filter/reduce¶

How many characters are there in total for strings that start with “a” in the sequence

["hello" "world" "again" "and" "again"]

(->> ["hello" "world" "again" "and" "again"]
(filter #(clojure.string/starts-with? % "a")))

("again" "and" "again")

(->> ["hello" "world" "again" "and" "again"]
(filter #(clojure.string/starts-with? % "a"))
(map count))

(5 3 5)

(->> ["hello" "world" "again" "and" "again"]
(filter #(clojure.string/starts-with? % "a"))
(map count)
(reduce + 0))

13


## Summary¶

• Functional programming prompts abstraction by exploring patterns that involve higher order functions.

• Map/filter/reduce functions allow very succint and composable code.

• Clojure provides different programming constructs from low-level loop/recur to sequence iteration with for-forms, to the higher level abstraction using map/filter/reduce.