---
title: Using Arrayprototypereduce to Reduce Conceptual Boilerplate for Problems on Arrays
localeTitle: Использование Arrayprototypereduce для уменьшения концептуальной котельной для проблем с массивами
---
Это довольно много! Это может быть просто названо как **Use `Array.prototype.reduce()` для решения проблем с массивом легко** или **`Array.prototype.reduce()` FTW!** , Это было бы намного легче читать и разбирать.

Но это не так. Циклы в JavaScript так же. Они не кратки, они заставляют вас биться вокруг куста на некоторое время. Как говорится в шутке, две вещи сложнее в информатике - [недействительность кеша](https://en.wikipedia.org/wiki/Cache_invalidation) , [именование вещей](https://www.quora.com/Why-is-naming-things-hard-in-computer-science-and-how-can-it-can-be-made-easier) и [ошибка «один за другим»](https://en.wikipedia.org/wiki/Off-by-one_error) .

И тогда существует опасность написания [асинхронного кода внутри цикла for без использования закрытия из IIFE](http://stackoverflow.com/questions/11488014/asynchronous-process-inside-a-javascript-for-loop) .

Эта статья начнется с утверждения - вы можете избежать использования цикла for-loop или while для решения любых связанных с `Array` проблем. Вместо этого вы можете решить все из них, используя `Array.prototype.reduce()` . Если вы хотите читать вперед; убедитесь, что вы знаете о рекурсивных функциях, а также некоторые классные функциональные инструменты, такие как `Array.prototype.map()` или `Array.prototype.filter()` .

Большие претензии требуют больших доказательств. Итак, давайте продемонстрируем, как мы можем привыкнуть к использованию метода `reduce()` .

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

Это предупреждение о клише; чтобы вы делали эти проблемы честной попыткой и не заглядывали в решения, прежде чем вы даже попытались.

Кроме того, если вы уже достаточно хорошо это понимаете, возможно, вам бы хотелось просмотреть этот фрагмент письма и предоставить обратную связь.

## Могу ли я уменьшить любую проблему, связанную с массивом?

Да, ты можешь! На самом деле проблема не требует наличия массива - _это просто проблема, когда вы можете создать промежуточный массив_ .

Давайте возьмем пример. Это довольно часто, чтобы создать [пул URL](https://en.wikipedia.org/wiki/Semantic_URL#Slug) из стандартной строки с белым цветом, например заголовки новостей, заголовки статей блога или даже вопросы о форумах вопросов и ответов.

Скажем, мы должны написать функцию полезности, которая создает этот пул. Вероятно, вы могли бы написать что-то вроде этого:
```
function createSlug(str){ 
  return str.split(" ").reduce(function(prev, next){ 
    return prev.concat(<a href='https://signalvnoise.com/posts/3124-give-it-five-minutes' target='_blank' rel='nofollow'>next.toLowerCase()]); 
  }, []) 
  .join("-"); 
 } 
```

Не верьте мне на слово! Идите дальше, и протестируйте его в своей консоли с помощью некоторого ввода, например, «Лев, наконец, выигрывает Freaking Oscar!». Посмотрите, что он возвращает. Я буду ждать ... сделано? Хорошо, двигаемся дальше.

Да, это не надежная реализация. Он не заботится о некоторых случаях кросс, также предполагает, что соединение должно происходить с `"-"` .

Но это начало. Обратите внимание на то, как использование `reduce` выводит шаблон из вашего пути - действие происходит только на линии:
```
return prev.concat([next.toLowerCase()]); 
```

В этом суть функциональности, которую мы хотим. На самом деле, мы настолько уверены в его удивительности, что мы запустим тело `function` с `return` !

Вы вполне можете себе представить, что это похоже на темную магию. Убедитесь, что это не реакция коленного рефлекса, потому что вы слишком привыкли писать петли. Просто \[дайте ему пять минут!

Если приведенный выше код не был ясен, вам нужно понять, как работает `reduce` . И _понимая_ , я имею в виду, знаю это как тыльная сторона твоей руки.

## Но я НЕ ПОНИМАЮ

Ну, не бойся! В ближайшие несколько минут вы собираетесь `reduce` ниндзя.

Каждая функция JavaScript имеет три вещи, которые вам нужно знать, чтобы понять, как работает эта функция:

*   Вход
*   Выход
*   Контекст выполнения

Да, я вижу, что вы открываете официальную [документацию MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) на новой вкладке! Все в порядке, сначала прочтите это. Я серьезно, это не шутка.

Вы должны всегда начинать с официальной документации, чтобы что-то понимать.

Хорошо, теперь, когда вы в замешательстве с `prev` и `next` внутри обратного вызова; вы готовы следовать тексту здесь.

`Array.prototype.reduce()` принимает обратный вызов и начальное значение в качестве входных аргументов (начальное значение важно. Многие разработчики забывают правильно `Array.prototype.reduce()` начальное значение и `Array.prototype.reduce()` свой код).

Как вы, наверное, видели в документации, требуется несколько дополнительных, но необязательных аргументов. Но об этом чуть позже. Предполагая, что `arr` - произвольный массив.
```
arr.reduce(function(){}, initialValue); 
```

Теперь давайте посмотрим на функцию обратного вызова, которая является первым аргументом `reduce` . Этот обратный вызов, в свою очередь, принимает два аргумента. Эти два аргумента вызываются в официальной документации как `prev` и `next` . Лично я не думаю, что эти имена оправдывают их истинную природу.

Таким образом, в этом тексте мы будем ссылаться на них как `acc` , чтобы представлять накопленную ценность; и `item` , для обозначения текущего элемента, к которому осуществляется доступ.

С этим до сих пор, вот что такое `reduce` должен выглядеть следующим образом :
```
arr.reduce(function(acc, item){ 
 /* here you have to complete the function */ 
 }, initialValue); 
```

Теперь давайте выясним, какова будет ценность этих `acc` и `item` . Ранее мы упоминали, что `reduce` является заменой итеративных конструкций.

Разумеется, `reduce` приведет к вашей пользовательской функции обратного вызова; и итерации по массиву, на который был вызван метод `reduce` .

Вместо того, чтобы описывать их, давайте спросим JS-механизм выполнения, что это такое!
```
var arr = <a href='http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/' target='_blank' rel='nofollow'>10, 20, 30, 60]; 
 arr.reduce(function(acc, item){ 
   console.log(acc, item); 
 }, 0); 
```

Выполнение вышеуказанного в браузере или консоли узла даст вам это как вывод:
```
0 10 
 undefined 20 
 undefined 30 
 undefined 60 
```

Обратите внимание, что количество выходов такое же, как количество элементов в массиве `[10, 20, 30, 60]` . Фактически, он печатает элементы массива!

Итак, мы можем вывести, что `reduce()` принимает ваш собственный обратный вызов и выполняет его для каждого элемента массива. При этом он делает текущий элемент доступным для пользовательского обратного вызова как аргумент `item` .

Но как насчет `acc` ? Мы видим, что кроме первой строки, когда `item = 10` , она не `undefined` . В первой строке, соответствующей первой итерации, ее значение равно `initialvalue` значению.

Короче говоря, наш `acc` acculator, не накапливается!

Но тогда, как мы его накапливаем? Попробуем выполнить это:
```
var arr = [10, 20, 30, 60]; 
 arr.reduce(function(acc, item){ 
   console.log(acc, item); 
   return acc; 
 }, 0); 
```

На этот раз выход изменится на:
```
0 10 
 0 20 
 0 30 
 0 60 
```

Как вы можете видеть, значение `acc` будет оставаться постоянным во всем. И это ожидается - мы не изменяем значение `acc` любом месте пользовательского обратного вызова. Мы возвращаем любые `reduce` доступные на данной итерации.

Но мы кое-что поняли - значение `acc` для текущей итерации было бы `return` значением из пользовательского обратного вызова из предыдущей итерации. И, в конечном счете, когда итерация закончится, окончательное значение `acc` будет возвращено путем `reduce` вызова.

Это оставляет только одну важную часть в нашем понимании - значение контекста исполнения, или \[ `this` !

Итак, мы снова подходим к нашему дружественному соседу, консоли JS и выполняем это:
```
var arr = <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode' target='_blank' rel='nofollow'>10, 20, 30, 60]; 
 arr.reduce(function(acc, item){ 
   console.log(acc, item, this); 
   return acc; 
 }, 0); 
```

Если вы находитесь в \[строгом режиме, он будет возвращен `undefined` как значение `this` . В противном случае, в браузере, он будет указывать на объект [`window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) как на `this` . Можем ли мы переопределить и установить его самостоятельно, используя [`bind`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind) ? Конечно! просто используйте `bind` с обратным вызовом:
```
var arr = <a href='https://en.wikipedia.org/wiki/Loop_invariant' target='_blank' rel='nofollow'>10, 20, 30, 60]; 
 arr.reduce(function(acc, item){ 
   console.log(acc, item, this); 
   return acc; 
 }.bind(arr), 0); 
```

Я связал массив `arr` ; но вы можете установить его на любой объект в вашей среде.

## Понимание сокращения

Подведем итог нашему пониманию этой функции `reduce` для удобства:

*   Сокращение принимает пользовательский обратный вызов в качестве его первого аргумента, а некоторое начальное значение - его второй аргумент.
*   Важно не забывать о втором аргументе, начальном значении; и мы явно устанавливаем его при его использовании.
*   Входные аргументы для пользовательского обратного вызова - накопленное значение `acc` ; и текущий элемент в массиве, `item` .
*   Значение `acc` для следующей итерации будет возвращенным значением внутри обратного вызова в текущей итерации.
*   Весь смысл использования `reduce()` , чтобы сформировать `acc` должным образом; чтобы вернуть его, наконец, из вызова `reduce()` .

Не пытайтесь вспомнить их, набивая зубы! Вместо этого давайте вспомним их, применив их в реальном коде.

## Использование сокращения

Давайте начнем простую операцию Array с вершины нашего _максимального поиска в массиве_

Для краткости я предполагаю, что это целочисленный массив.

Чтобы сформировать решение, нам нужно подумать о том, как сформировать `acc` поскольку `reduce` принимает наш обратный вызов и итерации по массиву.

Идея, которую я нахожу полезной, заключается в том, чтобы думать в терминах \[loop-инвариантов. Мы хотим придумать формулировку, которая не имеет значения, какой размер или содержание массива; `acc` всегда должен иметь максимальное значение.

Скажем, мой массив `[20, 50, 5, 60]` . После двух итераций; `item` будет равен `5` а `acc` должен быть `max(20, 50) = 50` .

Единственный способ `acc` всегда получает максимум _пройденного subarray до тех пор_ , если мы всегда выбираем максимум текущего `item` и `acc` - и возвращаем его победителем!

Итак, вот как выглядит функция:
```
var arr = [20, 50, 5, 60]; 
 arr.reduce(function(acc, item){ 
  return Math.max(acc, item); 
 }, 0); 
```

Может возникнуть соблазн переписать его следующим образом, в сочетании с принципами функционального программирования;
```
var arr = [20, 50, 5, 60]; 
 arr.reduce(Math.max, 0); 
```

но это не сработает и вернет `NaN` . Вот причина: `acc` и `item` не являются **единственными** аргументами пользовательского обратного вызова. Когда вы вызываете [`Math.max()`](//forum.freecodecamp.com/t/javascript-math-max/14682) пытается вызвать его по нечисловым аргументам, что приводит к `NaN` .

Обратите внимание, что я не задумывался о том, чтобы выбрать начальное значение. Я просто выбрал его как `0` ; что приводит к ошибке!

Итак, что, если мой массив состоит из значений меньше нуля? Скажем, `arr = <a href='https://en.wikipedia.org/wiki/Least_common_multiple' target='_blank' rel='nofollow'>-7, -56, -5, -2]` . Возвращаемое значение будет равно `0` , что даже не присутствует в массиве `arr` .

Вместо этого мы должны выбрать минимально возможное значение для начального значения.
```
var arr = [-20, -50, -5, -60]; 
 arr.reduce(function(acc, item){ 
  return Math.max(acc, item); 
 }, -Infinity); 
```

Мы добираемся туда. Мы должны оттачивать наши навыки по другой проблеме, связанной с Array. Просто повеселиться, давайте пойдем немного жестче.

Скажем, мы должны найти \[LCM массива целых чисел. Теперь, из теории, мы знаем, что LCM двух чисел будет их продуктом, разделенным их [HCF](https://en.wikipedia.org/wiki/Greatest_common_divisor) .

Существует алгоритм Eucledian для нахождения HCF; и в изобилии их реализация. Нет смысла тратить свое время на то, чтобы написать функцию HCF, когда вы можете написать ее самостоятельно или найти ее.

Скорее, давайте посмотрим, как увеличить LCM двух чисел в LCM из нескольких чисел. Newsflash - это не продукт целого массива, разделенного их HCF. Нету. Это было бы математически неправильно.

LCM из трех чисел будет LCM первых двух чисел; затем LCM первого LCM с оставшимся числом. Аналогичным образом, вы можете сформулировать стратегию, чтобы сначала найти LCM суб-массива, затем взять другое число и найти его LCM с первым LCM.

Итак, как мы формулируем решение? Нам нужно думать о `acc` в середине итерации. Окончательный `acc` должен быть LCM всего массива, без сомнения. Но и в течение `nth` итерации; `acc` должен удерживать LCM элементов `(n-1)` пройденных до сих пор.

И да, начальное значение. Это должно быть число, чей LCM с другим номером будет другим номером. Ясно, что это `1` .

Давайте напишем наше решение для `reduce` .
```
var arr = <a href='http://www.freecodecamp.com/challenges/symmetric-difference' target='_blank' rel='nofollow'>1, 2, 3, 4, 5, 6]; 
 arr.reduce(function(acc, item){ 
  return acc * item / hcf(acc, item); 
 }, 1); 
```

Я предполагаю, что `hcf()` доступна в среде. Я выбрал записи таким образом; он должен вернуть `60` качестве ответа.

## Больше уменьшить

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

Он отлично способен двигаться дальше и дальше. Сейчас мы будем иметь дело с некоторыми сложными примерами.

Скажем, вы хотите сгладить вложенные массивы. И да, прежде чем вы начнете прыгать вверх-вниз на своем месте - гнездование может быть любым произвольным уровнем глубины.

Например, мы могли бы взять этот массив для проверки нашего кода.
```
var arr = [[1, 2, 3], ['cat', 'dog', ['fish', 'bird'], [[[]]]]]; 
```

Это выглядит достаточно сложным для начала - вложенные массивы, пустые вложенные массивы с различной глубиной.

Выход должен быть `[1, 2, 3, 'cat', 'dog', 'fish', 'bird']`

Пришло время сформулировать стратегию. Нам явно нужно различать массив и элемент. Кроме того, `acc` должен быть массивом, сформированным на протяжении итерации; что означает, что начальное значение будет пустым массивом `[]` .

Во всем коде функции обратного вызова мы просто извлекаем содержимое из `item` , который может быть глубоко вложенным массивом; и мы бы `Array.prototype.concat()` это значение `acc` . Лучше использовать `concat()` над `Array.prototype.push()` ; потому что `push()` изменяет исходный массив; а `concat()` создает новый массив и возвращает его.

И так как мы не знаем уровня гнездования в любой момент времени; мы должны перевести вызов пользовательского обратного вызова рекурсивно. Значит, мы должны записать его где-то еще и назвать его по имени внутри `reduce()` .
```
var arr = [[1, 2, 3], ['cat', 'dog', ['fish', 'bird'], [[[]]]]]; 
 
 function flattenArray(arr) { 
  return arr.reduce(function(acc, item){ 
    if(Array.isArray(item)){ 
      return acc.concat(flattenArray(item)); // recursively call to flatten nested array 
    return acc.concat(item); // this does the ordering. If you want reverse ordered output, just reverse it! 
  }, []) 
 
 } 
 
 // call it like this 
 flattenArray(arr); 
```

Конечно, для этого требуется некоторый фон в рекурсивных функциях; но это не так сложно подобрать, по сравнению с вопросом об этом длинном!

Да, поиграй с этим. Но обратите внимание, как мы можем просто написать 3-4 строки чистых функций, содержащих несколько простых рекомендаций, - и сделать что-то столь же сложное, как это надежно. Это доступно для чтения и обслуживания.

Например, если вы хотите позже или позже изменить логику логики (скажем, вы хотите, чтобы в верхнем регистре была строка или закодирована некоторая строка); вы можете легко определить, где изменить. Фактическое гнездование происходит внутри условия `if` . И способ, которым мы использовали вызов `reduce` там - он поддерживает порядок элементов, поскольку они находятся в массиве.

Давайте возьмем еще один, казалось бы, сложный пример, и поднимем его на колени, разрушая меч `reduce` !

Мы должны выяснить \[симметричные различия двух или более массивов. Это выглядит пугающе; но тогда вы начинаете думать.

Каково было бы первоначальное значение? Конечно, мы формируем массив; поэтому для начала это будет пустой массив `<a href='https://github.com/reactjs/redux' target='_blank' rel='nofollow'>]` . Тогда есть `acc` - поскольку наше окончательное решение будет содержать массив дифф-й изд; это тоже будет массив. Это приведет к накоплению симметричных разностей найденных массивов.

Чтобы быть ясным, эта функция может принимать произвольное количество массивов; поэтому мы должны преобразовать их все в массив массивов для легкой манипуляции.
```
function symDiff(args){ 
  // convert args to an Array 
  var argsArray = Array.prototype.slice.call(arguments); 
 
  // now do the reduce magic! 
  argsArray.reduce(function(acc, item){ 
    return acc 
      .filter(function(itemInAcc){ 
        return item.indexOf(itemInAcc) === -1; 
      }) 
      .concat(item.filter(function(itemInItem){ 
        return acc.indexOf(itemInItem) === -1; 
      })); 
  }. []); 
 } 
```

Да, я знаю. Он выглядит большим. Итак, давайте посмотрим, можем ли мы сделать рефакторинг, чтобы сделать его маленьким. Обратите внимание, что обе функции `filter` выполняют такую ​​же работу; за исключением измененного набора пар аргументов. Круто! Давайте создадим отдельную функцию и дважды вызовите ее с этими аргументами.
```
function symDiff(args){ 
  // convert args to an Array 
  var argsArray = Array.prototype.slice.call(arguments); 
 
  // now do the reduce magic! 
  argsArray.reduce(function(acc, item){ 
    var funWithFiltering = function(arr1, arr2){ 
      return arr1.filter(function(itemInArr1){ 
        return arr2.indexOf(itemInArr1) === -1; 
      }); 
    }; 
 
    return funWithFiltering(acc, item).concat(funWithFiltering(item, acc)); 
  }. []); 
 } 
```

Это выглядит лучше. Но есть еще одна проблема. Это сохранит дубликаты в массиве. Если это не требуется, мы могли бы просто написать другую функцию, используя `reduce` чтобы удалить дубликаты.
```
function removeDuplicates(arr){ 
  arr.filter(item, index, self){ 
    // Keep only the first instance of the array, as given by indexOf() 
    // Remove other elements from Array 
    return self.indexOf(item) === index; 
  } 
 } 
```

Мы больше не можем этого игнорировать. Я использую `filter` , обещая использовать `reduce` , не так ли? Причина проста: `filter` можно записать с `reduce` . В самом деле, любая операция массива, теоретически; могут быть реализованы с помощью `reduce()` .

Попробуйте! Реализовать `map` и `filter` с `reduce` . Вы также должны позаботиться о дополнительных аргументах.

## Завершение

Это было довольно много! Но я думаю, что я сделал сильный случай использования `reduce` всякий раз, когда вы хотите использовать цикл, чтобы это сделать. Будьте приучены к нему, как к своей первой природе.

Как только у вас возникнет проблема с некоторыми преобразованиями строк или манипулированием массивами; начать с написания
```
return arr.reduce(function(acc, item){_}, _); 
```

А затем заполните пробелы. Когда вы используете `reduce()` , вы думаете о взаимодействии каждого элемента с другим элементом. Вы формируете результат, acculumating от начала до конца.

Структура \[Redux охватывает принцип `reduce` и набирает большую популярность в веб-дизайне.

Также обратите внимание на другую важную особенность - `reduce` силы или направляйте вас, чтобы сформировать свое решение, не изменяя ничего существующего. Например, в последнем примере; мы фильтровали и конкатенировали - но мы знали, что это будет работать так, как есть; потому что первый набор операций не изменил ни один из `acc` или `item` внутри этой итерации.

Это было бы отличным временем для вас, чтобы параметр `initialValue` [необязательным](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Parameters) . Вам не нужно его явно указывать.

Если вы опустите это; для первой итерации `acc` будет первым элементом в массиве, а `item` будет вторым элементом в массиве. Это означает, что мы можем написать сумму утилиты массива, просто опустив ее. Или нам не нужно думать о `-Infinity` в случае нахождения максимального значения в массиве - он будет работать нормально, если мы удалим начальное значение.

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

Если у вас возникнут какие-либо дополнительные вопросы или предложения, присоединитесь к нашему [часту](https://gitter.im/FreeCodeCamp/FreeCodeCamp) ; и расскажите, как вы `reduce` !