До недавнего времени я знал только, как использовать сокращение для его наиболее распространенной цели - суммирования всех элементов в массиве. Я знал, что он способен на большее, но погрузился в его многочисленные применения только тогда, когда начал готовиться к техническим собеседованиям.

Стоит разобраться в тонкостях его использования не только для суммирования значений, но и для сглаживания массивов, суммирования значений в массиве объектов и даже обратного преобразования строки.

Reduce, как map и filter, является функцией более высокого порядка. Функция более высокого порядка - это функция, которая принимает функцию в качестве аргумента или возвращает функцию в качестве вывода. Хотя карта и фильтр довольно просты и интуитивно понятны, сокращение немного сложнее. На каждую итерацию влияет возвращаемое значение каждой предыдущей итерации, и отслеживание итераций в уме может потребовать некоторой практики - вроде рекурсии.

Как это работает

Метод reduce принимает два параметра: функцию reducer (которая является обратным вызовом) и необязательное начальное значение. Редуктор выполняется для каждого члена вызывающего массива, что приводит к единственному выходному значению.

Чтобы продемонстрировать это, давайте посмотрим, как уменьшить без использования функции «уменьшить» более высокого порядка. Важно отметить, что функция-редуктор, которая передается в качестве обратного вызова, принимает 4 параметра: аккумулятор, currentValue, currentIndex, sourceArray. Ниже аккумулятор начинается с его начального значения (0) и постоянно увеличивается на элемент по каждому индексу.

let arr = [1,2,3,4,4,5]; //sourceArray
let sum = 0; //initialValue, used to start accumulation
for(let elem of arr){
	sum = sum + elem;
//accumulator + currentIndex (elem of array)
    //0 + 1   - initial value plus elem
        //1 + 2  - sum of previous iteration plus next elem in array
            //3 + 3 
                //6 + 4 
                    //10 + 4 
                        //14 + 5 
                            //19 - returns after last elem
}
console.log(sum) //prints 19

Теперь, понимая, как работает обратный вызов reducer, мы можем упростить процесс, используя функцию reduce. Здесь мы проходим через массив и накапливаем так же, но немного чище. Вот еще одна очень крутая анимация, демонстрирующая это.

let sum = (arr) => {
    return arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0 //optional initialValue, defaults 0)
    //accumulator + currentValue (each elem of array)
    //0 + 1   - initial value plus elem
        //1 + 2  - sum of previous round plus next elem in array
            //3 + 3 
                //6 + 4 
                    //10 + 4 
                        //14 + 5 
                            //19 - returns after last elem
}
console.log(sum([1,2,3,4,4,5])) //prints 19

Другие сценарии и варианты использования

Хотя сокращение, по-видимому, чаще всего используется для суммирования значений в массиве, стоит хорошо разобраться, чтобы использовать его в различных сценариях. Ниже приведены лишь несколько примеров использования сокращения.

Создать объекты

Подсчет частоты элемента в массиве - важная концепция, которая возникает во многих алгоритмических проблемах. В то время как «цикл for» является прекрасным вариантом перебора, сокращение может быть более элегантным и потенциально более эффективным способом его решения.

Ниже я использую как цикл, так и сокращение, чтобы подсчитать частоту букв в строке. Это также будет работать с массивом.

В виде цикла:

let str = 'banana';
let obj = {};
for (let char of str){
	obj[char] ? obj[char] += 1 : obj[char] = 1
	//if the key exists, increment its value by one; otherwise initiate key with a      value of 1
}
console.log(obj) // { b: 1, a: 3, n: 2 }

Как уменьшить:

let str = 'banana';
const sumByChar = str.split('').reduce((acc, char) => ({
		//use split to convert string to array of characters
    
	   ...acc,
		//using the spread operator on the accumulator will
                give us access to all the properties in the object
          [char]: (acc[char] || 0) + 1,
		// creating the element as a key - initializing to 1
                if it does not exist, incrementing by 1 if it does
      }), {});
      //initial value is an empty object
console.log(sumByChar) // { b: 1, a: 3, n: 2 }

Переверните строку или массив с помощью сокращения

Когда меня впервые попросили перевернуть строку после буткемпа, я подумал, что использование встроенной функции реверса было очевидным ответом. Я быстро понял, что интервьюеры, скорее всего, не примут это как ответ. Цикл казался следующим очевидным решением, и вскоре я узнал, что использование reduce было бы более элегантным вариантом.

В виде цикла:

let str = ''
for(let char of str){
	str = char + str
	//starts as empty string and accumulates one by one
	//'' = h + ''
	//h  = e + h
	//eh = l + eh
	//leh = l + leh
	//lleh = o + lleh
	//olleh
}
console.log('hello') // prints olleh

Как уменьшить:

let reversed = (str) => str.split('').reduce((acc,char) => char + acc)
	//accumulator starts empty and adds on one by one.  if string, use .split('') to convert to array.
	//'' = h + ''
	//h  = e + h
	//eh = l + eh
	//leh = l + leh
	//lleh = o + lleh
	//olleh
console.log(reversed('hello')) // prints olleh

Свести массив

Если у вас есть массивы внутри массива по какой-либо причине, вы можете объединить их в один «сплющенный» массив. Опять же, хотя есть встроенный «плоский» метод, который вы обычно можете использовать, на собеседовании вы, возможно, не сможете использовать эту встроенную функцию. Reduce может решить эту проблему с помощью рекурсии. Это было немного неприятно для меня, но я обещаю, что это хорошая практика как для сокращения, так и для рекурсии!

let flattenArray = (arr) => {
    return arr.reduce((total, curr) => {  
	return total.concat(Array.isArray(curr) ? flattenArray(curr) : curr);
	
      //if the value is an array, the recursively call reduce until next nested array
     //if the value is not an array, then just concatenate the value to the flattened array.
				
    //total [] curr 2
           //total [ 2 ] curr 1
           //total [ 2, 1 ] curr [ 3, 10, [ 12 ] ]
    //total [] curr 3
           //total [ 3 ] curr 10
           //total [ 3, 10 ] curr [ 12 ]
    //total [] curr 12
    
    //returns [ 2, 1, 3, 10, 12 ]
    }, []);
    //initial value is an empty array
}
console.log(flattenArray([2, 1, [3, 10, [12]]])) 
// returns [2, 1, 3, 10, 12]

Я надеюсь, что эти разбивки и примеры помогут вам понять, как работает сокращение и как его реализовать, помимо суммирования значений!

Ресурсы

Https://eloquentjavascript.net/05_higher_order.html

Https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

Https://blog.bitsrc.io/understanding-higher-order-functions-in-javascript-75461803bad

Https://blog.khanacademy.org/lets-reduce-a-gentle-introduction-to-javascripts-reduce-method/

Https://medium.com/@vmarchesin/using-array-prototype-reduce-in-objects-using-javascript-dfcdae538fc8

Https://www.digitalocean.com/community/tutorials/js-finally-understand-reduce#flattening-an-array-using-reduce