map()
Method to Extract Data from an Arrayfilter()
Method to Extract Data from an Arrayreduce()
Method to Analyze Data of an Arraysort()
Method without changing the Original ArrayFunctional programming is a style of programming where solutions are simple, isolated functions, without any side effects outside of the function scope: INPUT -> PROCESS -> OUTPUT
.
Functional programming is about:
The functional programming software development approach breaks a program into small, testable parts. Functional programming is a good habit. It keeps your code easy to manage, and saves you from sneaky bugs.
filter()
, the callback function tells JavaScript the criteria for how to filter an array.var over18 = [45, 4, 9, 16, 25].filter((value) => {
return value > 18;
});
First class functions = Functions that can be assigned to a variable, passed into another function, or returned from another function just like any other normal value. In JavaScript, all functions are first class functions.
Higher order functions = functions that take a function as an argument, or return a function as a return value.
lambda functions = functions that are passed in to another function or returned from another function.
Here’s a simple example:
// Functions that returns a string representing a cup of green tea, black tea
const prepareGreenTea = () => 'greenTea';
const prepareBlackTea = () => 'blackTea';
/*
Given a function (representing the tea type) and number of cups needed, the
following function returns an array of strings (each representing a cup of
a specific type of tea).
*/
const getTea = (prepareTea, numOfCups) => {
const teaCups = [];
for (let cups = 1; cups <= numOfCups; cups += 1) {
const teaCup = prepareTea();
teaCups.push(teaCup);
}
return teaCups;
};
/* We need to prepare 27 cups of green tea and 13 cups of black tea and store them in tea4GreenTeamFCC and tea4BlackTeamFCC variables,respectively. */
const tea4GreenTeamFCC = getTea(prepareGreenTea, 27);
const tea4BlackTeamFCC = getTea(prepareBlackTea, 13);
console.log(
tea4GreenTeamFCC, // [ 'greenTea', 'greenTea', ... ]
tea4BlackTeamFCC // [ 'blackTea', 'blackTea', ... ]
);
An imperative style in programming is one that gives the computer a set of statements to perform a task. In contrast, functional programming is a form of declarative programming. You tell the computer what you want done by calling a method or function.
For example, array method splice()
can be very hazardous because it modifies the array on which is called (and it also returns the removed items from the array):
var fruits = ['Banana', 'Orange', 'Apple', 'Mango', 'Kiwi'];
var appleIndex = 2;
var fruitsBeforeApple = fruits.splice(0, appleIndex);
console.log(fruitsBeforeApple); // ['Banana', 'Orange']
console.log(fruits); // ['Apple', 'Mango', 'Kiwi']
var fruitsAfterApple = fruits.splice(appleIndex + 1);
console.log(fruitsAfterApple); // WRONG: [] instead of ["Mango", "Kiwi"]
console.log(fruits); // [ 'Apple', 'Mango', 'Kiwi' ]
Note: splice()
first parameter defines the index, while the second parameter defines how many elements should be removed from that index (including the element on that index). If the second parameter is not mentioned, the method will remove all the elements after speciefied index (including the element on that index):
var fruits = ['Banana', 'Orange', 'Apple', 'Mango', 'Kiwi'];
var appleIndex = 2;
var fruitsAfterApple = fruits.splice(appleIndex + 1);
console.log(fruitsAfterApple); // [ 'Mango', 'Kiwi' ]
console.log(fruits); // [ 'Banana', 'Orange', 'Apple' ]
One way to solve the earlier problem is to call var fruitsAfterApple = fruits.splice(1)
without specifying the old position of Apple element. However, our list of fruits will still be modified:
var fruits = ['Banana', 'Orange', 'Apple', 'Mango', 'Kiwi'];
var appleIndex = 2;
var fruitsBeforeApple = fruits.splice(0, appleIndex);
console.log(fruitsBeforeApple); // ['Banana', 'Orange']
console.log(fruits); // ['Apple', 'Mango', 'Kiwi']
var fruitsAfterApple = fruits.splice(1);
console.log(fruitsAfterApple); // CORRECT: [ 'Mango', 'Kiwi' ]
console.log(fruits); // [ 'Apple' ]
HOWEVER, a better solution would be to use slice()
instead of splice()
. slice()
DOES NOT modify the original array, it just returns a new array of elements which is a subset of the original array (without causing any side affects).
var fruits = ['Banana', 'Orange', 'Apple', 'Mango', 'Kiwi'];
var appleIndex = 2;
var fruitsBeforeApple = fruits.slice(0, appleIndex);
console.log(fruitsBeforeApple); // ['Banana', 'Orange']
console.log(fruits); // [ 'Banana', 'Orange', 'Apple', 'Mango', 'Kiwi' ]
var fruitsAfterApple = fruits.slice(appleIndex + 1);
console.log(fruitsAfterApple); // [ 'Mango', 'Kiwi' ]
console.log(fruits); // [ 'Banana', 'Orange', 'Apple', 'Mango', 'Kiwi' ]
One of the core principles of functional programming is to not change original arrays/objects/etc… Changes lead to bugs. It’s easier to prevent bugs knowing that your functions don’t change anything, including the function arguments or any global variable.
In functional programming, changing or altering things is called mutation, and the outcome is called a side effect. A function, ideally, should be a pure function, meaning that it does not cause any side effects.
Another principle of functional programming is to always declare your dependencies explicitly. This means if a function depends on a variable or object being present, then pass that variable or object directly into the function as an argument.
function incrementer(num) {
return num + 1;
}
A more complex example: where we don’t want to alter/modify our array used as a parameter:
/* WRONG SOLUTION - HERE WE DO ALTER OUR GLOBAL VARIABLE fruitsList */
var fruitsList = ['Orange', 'Apple', 'Mango', 'Kiwi'];
function add(localFruits, fruitName) {
localFruits.push(fruitName);
return localFruits;
}
var newFruitsList = add(fruitsList, 'Pineapple');
console.log(fruitsList); // ['Orange', 'Apple', 'Mango', 'Kiwi', 'Pineapple'] - Original List is modified
console.log(newFruitsList); // ['Orange', 'Apple', 'Mango', 'Kiwi', 'Pineapple']
/* GOOD SOLUTION - we make a copy of the array */
var fruitsList = ['Orange', 'Apple', 'Mango', 'Kiwi'];
function add(localFruits, fruitName) {
let newLocalFruits = [...localFruits];
newLocalFruits.push(fruitName);
return newLocalFruits;
}
var newFruitsList = add(fruitsList, 'Pineapple');
console.log(fruitsList); // [ 'Orange', 'Apple', 'Mango', 'Kiwi' ]
console.log(newFruitsList); // [ 'Orange', 'Apple', 'Mango', 'Kiwi', 'Pineapple' ]
/* GOOD, MORE ELEGANT SOLUTION */
var fruitsList = ['Orange', 'Apple', 'Mango', 'Kiwi'];
function add(localFruits, fruitName) {
return [...localFruits, fruitName];
}
var newFruitsList = add(fruitsList, 'Pineapple');
console.log(fruitsList); // [ 'Orange', 'Apple', 'Mango', 'Kiwi' ]
console.log(newFruitsList); // [ 'Orange', 'Apple', 'Mango', 'Kiwi', 'Pineapple' ]
Let’s look at a more complex example:
// Another Example - SOLUTION 01
var fruitsList = ['Orange', 'Apple', 'Mango', 'Kiwi'];
function add(fruitsList, fruitName) {
let newfruitsList = [...fruitsList];
newfruitsList.push(fruitName);
return newfruitsList;
}
function remove(fruitsList, fruitName) {
let newfruitsList = [...fruitsList];
let fruit_index = fruitsList.indexOf(fruitName);
if (fruit_index >= 0) {
newfruitsList.splice(fruit_index, 1);
return newfruitsList;
}
}
var newfruitsList = add(fruitsList, 'Banana');
var newerfruitsList = remove(fruitsList, 'Orange');
var newestfruitsList = remove(add(fruitsList, 'Pineapple'), 'Apple');
console.log(fruitsList); // [ 'Orange', 'Apple', 'Mango', 'Kiwi' ]
console.log(newfruitsList); // [ 'Orange', 'Apple', 'Mango', 'Kiwi', 'Banana' ]
console.log(newerfruitsList); // [ 'Apple', 'Mango', 'Kiwi' ]
console.log(newestfruitsList); // [ 'Orange', 'Mango', 'Kiwi', 'Pineapple' ]
// Another Example - SOLUTION 02
var fruitsList = ['Orange', 'Apple', 'Mango', 'Kiwi'];
function add(fruitsList, fruitName) {
return [...fruitsList, fruitName];
}
function remove(fruitsList, fruitName) {
return fruitsList.filter((fruit) => fruit !== fruitName);
}
var newfruitsList = add(fruitsList, 'Banana');
var newerfruitsList = remove(fruitsList, 'Orange');
var newestfruitsList = remove(add(fruitsList, 'Pineapple'), 'Apple');
console.log(fruitsList); // [ 'Orange', 'Apple', 'Mango', 'Kiwi' ]
console.log(newfruitsList); // [ 'Orange', 'Apple', 'Mango', 'Kiwi', 'Banana' ]
console.log(newerfruitsList); // [ 'Apple', 'Mango', 'Kiwi' ]
console.log(newestfruitsList); // [ 'Orange', 'Mango', 'Kiwi', 'Pineapple' ]
map()
Method to Extract Data from an ArrayThe map
method iterates over each item in an array and returns a new array containing the results of calling the callback function on each element. It does this without mutating the original array.
const users = [
{ name: 'John', age: 34 },
{ name: 'Amy', age: 20 },
{ name: 'camperCat', age: 10 },
];
const names = users.map((user) => user.name);
console.log(names); // [ 'John', 'Amy', 'camperCat' ]
// Usually, map() is used to replace a for loop
var names = [];
for (let i = 0; i < users.length; i++) {
names.push(users[i].name);
}
console.log(names); // [ 'John', 'Amy', 'camperCat' ]
Or, on a more complex example:
/* Instead of using a for loop to store multiple objects with fewer properties into an array
coming from another array of objects... */
var ratings = [];
for (let i = 0; i < watchList.length; i++) {
ratings.push({
title: watchList[i]['Title'],
rating: watchList[i]['imdbRating'],
});
}
/* We can use map() */
const ratings = watchList.map((item) => ({
title: item['Title'],
rating: item['imdbRating'],
}));
/* or even map() with parameter destructuring */
const ratings = watchList.map(({ Title: title, imdbRating: rating }) => ({
title,
rating,
}));
Array.prototype.map()
, or simply map()
, is a pure function, and its output depends solely on its inputs. Plus, it takes another function as its argument.
Note: The differences between forEach
and map
are:
Array.prototype.forEach()
— executes a provided function once for each array element and doesn’t actually return anything (undefined). forEach
does mutate/modifies each element of the array called upon.Array.prototype.map()
— creates a new array with the results of calling a provided function on every element in the calling array, without modifying the original array.let arr = [1, 2, 3, 4, 5];
arr.forEach((num, index) => {
return (arr[index] = num * 2);
});
console.log(arr); // [2, 4, 6, 8, 10]
let arr = [1, 2, 3, 4, 5];
let doubled = arr.map((num) => {
return num * 2;
});
console.log(arr); // [1, 2, 3, 4, 5]
console.log(doubled); // [2, 4, 6, 8, 10]
filter()
Method to Extract Data from an Arrayfilter()
calls a function on each element of an array and returns a new array containing only the elements for which that function returns true
. In other words, it filters the array, based on the function passed to it, without modifying the original array.
const users = [
{ name: 'John', age: 34 },
{ name: 'Amy', age: 20 },
{ name: 'camperCat', age: 10 },
];
const usersUnder30 = users.filter((user) => user.age < 30);
console.log(usersUnder30); // [ { name: 'Amy', age: 20 }, { name: 'camperCat', age: 10 } ]
On a more complex example: Retrieve a list of objects with only title
and rating
keys, where imdbRating
from the original array of objects is greater than or equal to 8.0. We will use filter()
, then map()
.
// The global variable
var watchList = [
{
Title: 'Inception',
Year: '2010',
Director: 'Christopher Nolan',
imdbRating: '8.8',
},
{
Title: 'Avatar',
Year: '2009',
Director: 'James Cameron',
imdbRating: '7.9',
},
];
var filteredList;
filteredList = watchList
.filter((item) => item['imdbRating'] >= 8.0)
.map((item) => ({
title: item['Title'],
rating: item['imdbRating'],
}));
console.log(filteredList); // [ { title: 'Inception', rating: '8.8' } ]
reduce()
Method to Analyze Data of an ArrayArray.prototype.reduce()
, or simply reduce()
, is the most general of all array operations in JavaScript. You can solve almost any array processing problem using the reduce
method. it’s possible to show that both filter
and map
can be derived as special applications of reduce
.
The reduce
method iterates over each item in an array and returns a single value (i.e. string, number, object, array). This is achieved via a callback function that is called on each iteration.
Example: Get the average IMDB Rating of the movies directed by Christopher Nolan. Recall from prior challenges how to filter
data and map
over it to pull what you need.
function getRating(watchList) {
let filteredRatings = watchList
.filter((item) => item['Director'] == 'Christopher Nolan')
.map((item) => Number(item['imdbRating']));
// console.log(filteredRatings);
var averageRating = filteredRatings.reduce((total, value) => total + value);
averageRating /= filteredRatings.length;
// console.log(averageRating);
return averageRating;
}
Or, another solution:
function getRating(watchList) {
var count = 0;
var averageRating =
watchList.reduce((sum, movie) => {
if (movie.Director == 'Christopher Nolan') {
count += 1;
return sum + parseFloat(movie.imdbRating);
}
return sum;
}, 0) / count;
return averageRating;
}
sort()
Method without changing the Original ArraySimple example:
// Solution 1
var globalArray = [5, 6, 3, 2, 9];
function nonMutatingSort(arr) {
let copyArr = [...arr];
return copyArr.sort((a, b) => {
return a - b;
});
}
nonMutatingSort(globalArray);
// Solution 2
var globalArray = [5, 6, 3, 2, 9];
function nonMutatingSort(arr) {
return [].concat(arr).sort((a, b) => {
return a - b;
});
}
nonMutatingSort(globalArray); // [ 2, 3, 5, 6, 9 ]
console.log(globalArray); // [ 5, 6, 3, 2, 9 ]
slice()
cities = ['Chicago', 'Delhi', 'London', 'Berlin'];
newCities = cities.slice(0, 3);
console.log(cities); // [ 'Chicago', 'Delhi', 'London', 'Berlin' ]
console.log(newCities); // [ 'Chicago', 'Delhi', 'London' ]
We can use slice()
method instead of splice()
as we saw earlier.
concat()
: originalArr.concat(attachedArr)
var first = [1, 2, 3];
var second = [4, 5];
var combined = first.concat(second);
console.log(first); // [ 1, 2, 3 ]
console.log(combined); // [ 1, 2, 3, 4, 5 ]
We can use concat()
method to add new elements to the end of an array (instead of using push
), if we don’t want to alter the original array: newArr = myArr.concat(newItem);
instead of myArr.push(newItem);
map()
, filter()
and reduce()
These were presented earlier.
split()
, join()
split()
method splits a string into an array of strings. It takes an argument for the delimiter, which can be a character to use to break up the string or a regular expression. For example, if the delimiter is a space, you get an array of words, and if the delimiter is an empty string, you get an array of each character in the string.
var str = 'Hello World';
var bySpace = str.split(' '); // ["Hello", "World"]
var otherString = 'How9are7you2today';
var byDigits = otherString.split(/\d/); // ["How", "are", "you", "today"]
The join
method is used to join the elements of an array together to create a string. It takes an argument for the delimiter that is used to separate the array elements in the string.
var arr = ['Hello', 'World'];
var str = arr.join(' '); // "Hello World"
Another example:
function sentensify(str) {
return str.split(/\W/).join(' ');
}
sentensify('May-the-force-be-with-you'); // "May the force be with you"
sentensify('The.force.is.strong.with.this.one'); // "The force is strong with this one"
sentensify('There,has,been,an,awakening'); // "There has been an awakening"
Another example:
Return the hyphenated version for an URL. Many content management sites (CMS) have the titles of a post added to part of the URL for simple bookmarking purposes. For example, if you write a Medium post titled “Stop Using Reduce”, it’s likely the URL would have some form of the title string in it (“…/stop-using-reduce”).
function urlSlug(title) {
return title
.split(/\W/) // [ '', 'Winter', 'Is', '', 'Coming' ]
.filter((val) => val) // [ 'Winter', 'Is', 'Coming' ]
.map((val) => val.toLowerCase()) // [ 'winter', 'is', 'coming' ]
.join('-'); // winter-is-coming
}
console.log(urlSlug(' Winter Is Coming')); // winter-is-coming
console.log(urlSlug('A Mind Needs Books Like A Sword Needs A Whetstone')); // a-mind-needs-books-like-a-sword-needs-a-whetstone
every()
and some()
The every
method works with arrays to check if every element passes a particular test. It returns a Boolean value - true
if all values meet the criteria, false
if not.
function checkPositive(arr) {
return arr.every((val) => val >= 0);
}
checkPositive([1, 2, 3, -4, 5]); // false
checkPositive([1, 2, 3, 4, 5]); // true
The some
method works with arrays to check if any element passes a particular test. It returns a Boolean value - true
if any of the values meet the criteria, false
if not.
[1, 2, 3, -4, 5].some((val) => val >= 0); // true
[-1, -2, -3, -4, -5].some((val) => val >= 0); // false
NOTE: Sounds silly, but should not be confused with indexing: ar = [1, [2, 4], 'a']; ar[1][1]; // returns 4
Example:
//Un-curried function
function unCurried(x, y) {
return x + y;
}
//Curried function
function curried(x) {
return function (y) {
return x + y;
};
}
//Alternative using ES6
const curried2 = (x) => (y) => x + y;
curried(1)(2); // Returns 3
curried(1); // Returns [Function]
curried(1)(5)(2); // Returns TypeError: curried(...)(...) is not a function