Original

Both map() and flatMap() take a function f as a parameter that controls how an input Array is translated to an output Array:

  • With map(), each input Array element is translated to exactly one output element, aka, f returns a single value

  • With flatMap(), each input Array element is translated to zero or more output elements, aka, f returns an Array of values.

An smiple implementation of flatMap:

1
2
3
4
5
6
7
8
9
10
11
12
function flatMap (arr, mapFunc) {
const result = []
for (const [index, value] of arr.entries()) {
const x = mapFunc(value, index, arr)
if (Array.isArray(x)) {
result.push(...x)
} else {
result.push(x)
}
}
return result
}

flatMap is simpler if mapFunc is only allowed to return Arrays, but we don’t impose this restriction here, because non-Array values are occasionally useful.

Filtering and mapping at the same time

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function processArray (arr, processFunc) {
return arr.map(x => {
try {
return { value: processFunc(x) }
} catch (e) {
return { error: e }
}
})
}

const results = processArray(myArray, myFunc)

const values = flatMap(results, result => result.value ? [result.value] : []) // here we use [result.value] to avoid destructing value if it is an array.
const errors = flapMap(results, result => result.error ? result.error : [])

Mapping to multiple values

The Array method map() maps each input Array element to one output. But if we want to map it to multiple output elements?

That becomes necessary in the following example: The React component TagList is invoked with two attributes

1
<TagList tags={['foo', 'bar', 'baz']} handleClick={x => console.log(x)} />

The attributes are:

  • An Array of tags, each tag being a string

  • A callback for handling clicks on tags

TagList is rendered as a series of links seperated by commas:

1
2
3
4
5
6
7
8
9
10
11
class TagList extends React.Component {
render () {
const { tags, handleClick } = this.props
return flatMap(tags, (tag, index) => [
...(index > 0 ? [', '] : []),
<a key={index} href="" onClick={e => handleClick(tag, e)}>
{tag}
</a>
])
}
}

Here each tag (except the first) provide two elements in the rendered Array

Arbitrary Iterables

flatMap can be generalized to work with arbitrary iterables

1
2
3
4
5
6
7
function* flatMapIter(iterable, mapFunc) {
let index = 0
for (const x of iterable) {
yield* mapFunc(x, index)
index++
}
}

flatMapIter function works with Arrays:

1
2
3
4
function fillArray () {
return new Array(x).fill(x)
}
console.log([...flatMapIter([1,2,3], fillArray)])

Implementing flatMap via reduce

You can use the Array method reduce to implement a simple version of flatMap

1
2
3
4
5
6
function flatMap (arr, mapFunc) {
return arr.reduce(
(prev, x) => prev.concat(mapFunc(x)),
[],
)
}

Related to flatMap: flatten

flatten is an operation that concatenates all the elements of an Array

1
2
> flatten(['a', ['b', 'c'], ['d']])
['a', 'b', 'c', 'd']

It can be implemented as follows:

1
const flatten = (arr) => [].concat(...arr)

So the following expressions are equivalent

1
2
flatten(arr.map(func))
flatMap(arr, x => x)