До недавнего времени я знал только, как использовать сокращение для его наиболее распространенной цели - суммирования всех элементов в массиве. Я знал, что он способен на большее, но погрузился в его многочисленные применения только тогда, когда начал готовиться к техническим собеседованиям.
Стоит разобраться в тонкостях его использования не только для суммирования значений, но и для сглаживания массивов, суммирования значений в массиве объектов и даже обратного преобразования строки.
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