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

  1. Use JEST as the testing framework to write single tests for all of the methods in this article
  2. More exercises can be found at github.com/you-dont-ne…