Detailed explanation of JavaScript Reduce

Detailed explanation of JavaScript Reduce

Learning this skill Reduce will open up a new world of programming for you

Learning This Reduce Skill and a Whole New World Will Open up for You 🎉

reduce is the most flexible JS array method because it can replace other array methods, such as map / filter / some / every , etc. It is also the most difficult method to understand. Many lodash methods can also be implemented with it. Learning reduce will give developers another functional and declarative perspective to solve problems, rather than the previous procedural or imperative perspectives.

One of the difficulties is to determine the type of acc ( accumulation ) and how to choose the initial value. In fact, there is a little trick that can help us find a suitable initial value. The type of the return value we want must be the same as the type of acc . For example, if the final result of the sum is a number, then acc should be a number type, so its initialization must be 0 .

Let's start by consolidating our understanding and usage of reduce .

map

According to the tips, the final return value of map is an array, so acc should also be an array, and an empty array can be used as the initial value.

/**
 * Use `reduce` to implement the builtin `Array.prototype.map` method.
 * @param {any[]} arr 
 * @param {(val: any, index: number, thisArray: any[]) => any} mapping 
 * @returns {any[]}
 */
function map(arr, mapping) {
 return arr.reduce((acc, item, index) => [...acc, mapping(item, index, arr)], []);
}

test

map([null, false, 1, 0, '', () => {}, NaN], val => !!val);

// [false, false, true, false, false, true, false]

filter

According to the tips, the final return value of filter is also an array, so acc should also be an array, and an empty array can be used.

/**
 * Use `reduce` to implement the builtin `Array.prototype.filter` method.
 * @param {any[]} arr 
 * @param {(val: any, index: number, thisArray: any[]) => boolean} predicate 
 * @returns {any[]}
 */
function filter(arr, predicate) {
 return arr.reduce((acc, item, index) => predicate(item, index, arr) ? [...acc, item] : acc, []);
}

test

filter([null, false, 1, 0, '', () => {}, NaN], val => !!val);

// [1, () => {}]

some

some returns false when the target array is empty, so the initial value is false .

function some(arr, predicate) {
 return arr.reduce((acc, val, idx) => acc || predicate(val, idx, arr), false)
}

test:

some([null, false, 1, 0, '', () => {}, NaN], val => !!val);
// true

some([null, false, 0, '', NaN], val => !!val);
// false

As a reminder, the two have no effect on the result but have performance differences. Putting acc in front is a short-circuit algorithm, which can avoid unnecessary calculations and thus has higher performance.

acc || predicate(val, idx, arr)

and

predicate(val, idx, arr) || acc

every

If every target array is empty, it returns true, so the initial value is true

function every(arr, predicate) {
 return arr.reduce((acc, val, idx) => acc && predicate(val, idx, arr), true)
}

findIndex

If findIndex target array is empty, it returns -1, so the initial value is -1.

function findIndex(arr, predicate) {
 const NOT_FOUND_INDEX = -1;

 return arr.reduce((acc, val, idx) => {
 if (acc === NOT_FOUND_INDEX) {
 return predicate(val, idx, arr) ? idx : NOT_FOUND_INDEX;
 }
 
 return acc;
 }, NOT_FOUND_INDEX)
}

test

findIndex([5, 12, 8, 130, 44], (element) => element > 8) // 3

pipe

1. Implement the following functions

/**
 * Return a function to make the input value processed by the provided functions in sequence from left the right.
 * @param {(funcs: any[]) => any} funcs 
 * @returns {(arg: any) => any}
 */
function pipe(...funcs) {}

Make

pipe(val => val * 2, Math.sqrt, val => val + 10)(2) // 12

This function can be used to implement some more complex processing

// Pick out the items whose val is positive, multiply their val by 0.1, then add up the val of all items, and finally get 3
const process = pipe(
 arr => arr.filter(({ val }) => val > 0), 
 arr => arr.map(item => ({ ...item, val: item.val * 0.1 })), 
 arr => arr.reduce((acc, { val }) => acc + val, 0)
);

process([{ val: -10 }, { val: 20 }, { val: -0.1 }, { val: 10 }]) // 3

2. Implement the following function, which can realize the function of the above pipe and return a function that accepts an indefinite number of parameters.

/**
 * Return a function to make the input values ​​processed by the provided functions in sequence from left the right.
 * @param {(funcs: any[]) => any} funcs 
 * @returns {(args: any[]) => any}
 */
function pipe(...funcs) {}

Make the following single test pass

pipe(sum, Math.sqrt, val => val + 10)(0.1, 0.2, 0.7, 3) // 12

Among them, sum has been realized

/**
 * Sum up the numbers.
 * @param args number[]
 * @returns {number} the total sum.
 */
function sum(...args) {
 return args.reduce((a, b) => a + b);
}

Reference answer

1. Returning a function that accepts a parameter

Omit the func step of filtering out non-functions

/**
 * Return a function to make the input value processed by the provided functions in sequence from left the right.
 * @param {(arg: any) => any} funcs
 * @returns {(arg: any) => any}
 */
function pipe(...funcs) {
 return (arg) => {
 return funcs.reduce(
 (acc, func) => func(acc),
 arg
 )
 }
}

2. Returning functions accept indefinite parameters

The func step of filtering out non-functions is also omitted.

/**
 * Return a function to make the input value processed by the provided functions in sequence from left the right.
 * @param {Array<(...args: any) => any>} funcs
 * @returns {(...args: any[]) => any}
 */
function pipe(...funcs) {
	// const realFuncs = funcs.filter(isFunction);

 return (...args) => {
 return funcs.reduce(
 (acc, func, idx) => idx === 0 ? func(...acc) : func(acc),
 args
 )
 }
}

Better writing performance, avoiding unnecessary comparisons and wasting CPU

function pipe(...funcs) {
 return (...args) => {
 // The first one has been processed, only the remaining ones need to be processed return funcs.slice(1).reduce(
 (acc, func) => func(acc),
 
 // First handle the special case as `acc`
 funcs[0](...args)
 )
 }
}

Pay attention to the pitfall of the second way of writing funcs[0](...args) . If the array is empty, it will explode because of the null pointer.

Implementing lodash.get

Implementing get makes the following example return 'hello world' .

const obj = { a: { b: { c: 'hello world' } } };

get(obj, 'abc');

Function signature:

/**
 * pluck the value by key path
 * @param any object
 * @param keyPath string dot-separated key path* @returns {any} target value*/
function get(obj, keyPath) {}

Reference answer

/**
 * Pluck the value by key path.
 * @param any object
 * @param keyPath string dot-separated key path* @returns {any} target value*/
function get(obj, keyPath) {
 if (!obj) {
 return undefined;
 }

 return keyPath.split('.').reduce((acc, key) => acc[key], obj);
}

Implementing lodash.flattenDeep

Although concat and spread operators can only flatten one layer, deep flattening can be achieved through recursion.

Method 1: Spread operator

function flatDeep(arr) {
 return arr.reduce((acc, item) => 
 Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item],
 []
 )
}

Method 2: concat

function flatDeep(arr) {
 return arr.reduce((acc, item) => 
 acc.concat(Array.isArray(item) ? flatDeep(item) : item),
 []
 )
}

Interesting performance comparison: the expansion operator takes 1098ms to execute 70,000 times, while concat can only execute 20,000 times in the same time.

function flatDeep(arr) {
 return arr.reduce((acc, item) => 
 Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item],
 []
 )
}

var arr = repeat([1, [2], [[3]], [[[4]]]], 20);

console.log(arr);
console.log(flatDeep(arr));

console.time('concat')
for (i = 0; i < 7 * 10000; ++i) {
 flatDeep(arr)
}
console.timeEnd('concat')

function repeat(arr, times) { let result = []; for (i = 0; i < times; ++i) { result.push(...arr) } return result; }

Filter out null values ​​in an object

accomplish

clean({ foo: null, bar: undefined, baz: 'hello' })

// { baz: 'hello' }

Answer

/**
 * Filter out the `nil` (null or undefined) values.
 * @param {object} obj
 * @returns {any}
 *
 * @example clean({ foo: null, bar: undefined, baz: 'hello' })
 *
 * // => { baz: 'hello' }
 */
export function clean(obj) {
 if (!obj) {
 return obj;
 }

 return Object.keys(obj).reduce((acc, key) => {
 if (!isNil(obj[key])) {
 acc[key] = obj[key];
 }

 return acc;
 }, {});
}

enumify

Simulate constant objects as TS enumerations

Implement enumify so that

const Direction = {
 UP: 0,
 DOWN: 1,
 LEFT: 2,
 RIGHT: 3,
};

const actual = enumify(Direction);

const expected = {
 UP: 0,
 DOWN: 1,
 LEFT: 2,
 RIGHT: 3,

 0: 'UP',
 1: 'DOWN',
 2: 'LEFT',
 3: 'RIGHT',
};

deepStrictEqual(actual, expected);

Answer:

/**
 * Generate enum from object.
 * @see https://www.typescriptlang.org/play?#code/KYOwrgtgBAglDeAoKUBOwAmUC8UCMANMmpgEw5SlEC+UiiAxgPYgDOTANsAHQdMDmAChjd0GAJQBuRi3ZdeA4QG08AXSmIgA
 * @param {object} obj
 * @returns {object}
 */
export function enumify(obj) {
 if (!isPlainObject(obj)) {
 throw new TypeError('the enumify target must be a plain object');
 }

 return Object.keys(obj).reduce((acc, key) => {
 acc[key] = obj[key];
 acc[obj[key]] = key;

 return acc;
 }, {});
}

Promise Serial Executor

By using reduce, we can make an indefinite number of promises execute serially, which can play a big role in actual projects. I won’t go into details here, please refer to my next article JS Request Scheduler.

expand

Please use jest as the testing framework and write unit tests for all the methods in this article. For more exercises, see github.com/you-dont-ne…

The above is the detailed content of the detailed explanation of the use of JavaScript Reduce. For more information about the use of JavaScript Reduce, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • 5 Basic Usage Examples of reduce() in JavaScript
  • JavaScript Array reduce() Method
  • 8 JS reduce usage examples and reduce operation methods
  • Summary of using the reduce() method in JS
  • js uses the reduce method to make your code more elegant
  • Detailed explanation of the basic usage of JavaScript reduce

<<:  CentOS7 64-bit installation mysql graphic tutorial

>>:  Packetdrill's concise user guide

Recommend

A designer complains about Hammer's official website again

Last year, the open letter was a huge hit, even a...

Seven ways to implement array deduplication in JS

Table of contents 1. Using Set()+Array.from() 2. ...

4 Practical Tips for Web Page Design

Related articles: 9 practical tips for creating we...

Vue large screen data display example

In order to efficiently meet requirements and avo...

Specific use of Docker anonymous mount and named mount

Table of contents Data volume Anonymous and named...

MySQL 5.7 JSON type usage details

JSON is a lightweight data exchange format that u...

SELinux Getting Started

Back in the Kernel 2.6 era, a new security system...

Detailed explanation of how to customize the style of CSS scroll bars

This article introduces the CSS scrollbar selecto...

How to deploy kafka in docker

Table of contents 1. Build Docker 2. Enter the co...

Example of Html shielding right-click menu and left-click typing function

Disable right-click menu <body oncontextmenu=s...

Detailed process of building mysql5.7.29 on centos7 of linux

1. Download MySQL 1.1 Download address https://do...

Solution to Vue's inability to watch array changes

Table of contents 1. Vue listener array 2. Situat...

Pure CSS to achieve left and right drag to change the layout size

Utilize the browser's non- overflow:auto elem...