Learning this one skill will open up a whole new world of programming
Learning This
Reduce
Skill and a Whole New World Will Open up for You 🎉
Reduce is the most flexible JS array method, because other methods that can replace arrays, such as map/filter/some/every, are also the most difficult method to understand, and many loDash methods can also be implemented with it. Learning reduce will give developers a Functional and Declarative approach to problem solving, instead of the Procedual or Imperative of the past. See my next article The Differences Between Procedural, Functional, Imperative, and Declarative Programming Paradigms for four Programming modes.
One of the difficulties is determining the type of ACC accumulation and how to choose an initial value. There is actually a trick that can help us find the right initial value. We want the type of return value to be the same as acc, for example, if the sum is a number, acc should be a number. So its initialization must be 0.
Let’s begin to consolidate our understanding and usage of reduce.
map
As a tip, map returns an array, so ACC should also be an array, starting with an empty array.
/**
* 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)], []);
}
Copy the code
test
map([null.false.1.0.' '.() = > {}, NaN].val= >!!!!! val);// [false, false, true, false, false, true, false]
Copy the code
filter
Filter returns an array, so acc should also be an array, using an empty array.
/**
* 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, []);
}
Copy the code
test
filter([null.false.1.0.' '.() = > {}, NaN].val= >!!!!! val);// [1, () => {}]
Copy the code
some
Some Returns false if the destination 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)}Copy the code
Testing:
some([null.false.1.0.' '.() = > {}, NaN].val= >!!!!! val);// true
some([null.false.0.' '.NaN].val= >!!!!! val);// false
Copy the code
As a reminder, the two have no impact on the results, but there is a performance difference. Acc is put in front because it is a short-circuit algorithm, which can avoid unnecessary calculation, so the performance is higher.
acc || predicate(val, idx, arr)
Copy the code
and
predicate(val, idx, arr) || acc
Copy the code
every
Every returns true if the array is empty, so the initial value is true
function every(arr, predicate) {
return arr.reduce((acc, val, idx) = > acc && predicate(val, idx, arr), true)}Copy the code
findIndex
FindIndex 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)
}
Copy the code
test
findIndex([5.12.8.130.44].(element) = > element > 8) / / 3
Copy the code
pipe
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) {}
Copy the code
make
pipe(val= > val * 2.Math.sqrt, val= > val + 10) (2) / / 12
Copy the code
Some more complex processes can be implemented with this function
// Select the term whose val is positive and multiply val by the coefficient 0.1, then add all the terms together to 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
Copy the code
2. Implement the following functions, which can realize the function of pipe and the number of parameters accepted by the return function can be variable
/**
* 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) {}
Copy the code
Make the following single test pass
pipe(sum, Math.sqrt, val= > val + 10) (0.1.0.2.0.7.3) / / 12
Copy the code
Where sum is implemented
/**
* Sum up the numbers.
* @param args number[]
* @returns {number} the total sum.
*/
function sum(. args) {
return args.reduce((a, b) = > a + b);
}
Copy the code
Refer to the answer
The return function takes an argument
Omit the func step that filters 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
)
}
}
Copy the code
The return function takes an indefinite parameter
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 ) } }Copy the code
Better performance writing method, avoid unnecessary comparison, waste of CPU.
function pipe(. funcs) {
return (. args) = > {
// The first one has already been processed
return funcs.slice(1).reduce(
(acc, func) = > func(acc),
// First handle special cases as' acc '
funcs[0] (... args) ) } }Copy the code
Funcs [0](… If the array is empty, it explodes because the pointer is null.
implementationlodash.get
Implementing GET causes the following example to return ‘Hello world’.
const obj = { a: { b: { c: 'hello world'}}}; get(obj,'a.b.c');
Copy the code
Function signature:
/**
* pluck the value by key path
* @param any object
* @param KeyPath String Key paths separated by dots *@returns {any} Target * /
function get(obj, keyPath) {}
Copy the code
Refer to the answer
/**
* Pluck the value by key path.
* @param any object
* @param KeyPath String Key paths separated by dots *@returns {any} Target * /
function get(obj, keyPath) {
if(! obj) {return undefined;
}
return keyPath.split('. ').reduce((acc, key) = > acc[key], obj);
}
Copy the code
implementationlodash.flattenDeep
Although it is possible to flatten only one layer using concat and extension operators, it is possible to achieve deep flatten through recursion.
Method one: Extend the operator
function flatDeep(arr) {
return arr.reduce((acc, item) = >
Array.isArray(item) ? [...acc, ...flatDeep(item)] : [...acc, item],
[]
)
}
Copy the code
Method 2: concat
function flatDeep(arr) {
return arr.reduce((acc, item) = >
acc.concat(Array.isArray(item) ? flatDeep(item) : item),
[]
)
}
Copy the code
Interesting performance comparison, the extension operator 70000 times 1098ms, the same time concat can only execute 20000 times.
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; }
Copy the code
Filter out null values in objects
implementation
clean({ foo: null.bar: undefined.baz: 'hello' })
// { baz: 'hello' }
Copy the code
The 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;
}, {});
}
Copy the code
enumify
Simulate constant objects as enumerations of TS
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);Copy the code
The answer:
/**
* Generate enum from object.
* @see https://www.typescriptlang.org/play?#code/KYOwrgtgBAglDeAoKUBOwAmUC8UCMANMmpgEw5SlEC+UiiAxgPYgDOTANsAHQdMDmAChjd0GAJQBuR i3ZdeA4QG08AXSmIgA *@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;
}, {});
}
Copy the code
Promise serial executor
Reduce allows us to make promises in serial quantities, which can play a big role in real projects. I won’t go into details here, but please refer to my next article JS Request scheduler.
expand
- Use JEST as the testing framework to write single tests for all of the methods in this article
- More exercises can be found at github.com/you-dont-ne…