Преобразование массива в одно значение с помощью сокращения

Довольно распространенная вещь, которую все мы, программисты, делаем: мы берем какой-то список данных в виде массива и нам нужно преобразовать его во что-то другое. Действительно мощный способ сделать это в JavaScript — использовать функцию сокращения, которая встроена во все массивы. Теперь, чтобы использовать сокращение, вам нужны две вещи. Первое, что вам нужно, это редуктор, а второе, что вам нужно, это начальное значение аккумулятора.

Вот как это работает. Допустим, у нас есть список чисел, и мы хотим сложить их все. Это классический пример редукции. Наш редуктор выглядит так. Он принимает два аргумента. Первый аргумент называется аккумулятором. Второй аргумент — это элемент массива, который вы собираетесь интегрировать в аккумулятор. Что вы собираетесь сделать, так это вернуть новый аккумулятор.

Уменьшить массив в один объект

Теперь, как это всегда работает, выражение сокращения здесь, все это является выражением. Это означает, что оценивается до определенного значения. Array.reduce всегда вычисляет конечное значение аккумулятора, поэтому все это будет просто 31.

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

Теперь, когда у нас есть этот список голосов, мы хотим превратить его в объект, который дает нам количество голосов для каждого названного фреймворка. Что ж, вы должны подумать про себя: «У меня есть массив, и я хочу превратить этот массив в объект. Эй, это работа для Reduce».

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

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

Когда мы запускаем это, мы видим, что у нас было три голоса за Angular, четыре голоса за React, один голос за Amber и один голос за отсутствие фреймворка.

Используйте функцию «Уменьшить» для фильтрации и сопоставления больших наборов данных

Я хотел бы немного поговорить о некоторых различных видах операций сокращения, которые довольно часто используются. На самом деле, некоторые из них настолько распространены и настолько повседневны, что даже не используют функцию сокращения. Есть и другие способы их реализации, но я бы хотел, чтобы вы продолжали думать о них как о сокращениях. Потому что они берут массив и преобразуют его во что-то другое. Самый распространенный из них — один из самых распространенных, допустим, у вас есть какой-то массив, и вам нужно преобразовать его в другой массив того же размера. У него такое же количество элементов, но с каждым элементом в этом массиве произошло какое-то преобразование. Например, почему бы нам не взять наш массив данных здесь? Мы собираемся преобразовать его в другой массив, где каждое значение удваивается. Можно сказать, что удвоение var равно data.reduce. Начальным значением для нашего аккумулятора будет новый массив. Все, что мы собираемся сделать, это умножить значение accumulator.push на два. Затем нам нужно убедиться, что мы возвращаем накопитель.

Если мы скажем console.log мои удвоенные данные, мы увидим, что мы действительно превратили каждое из этих значений в его удвоение. Если вы обратите внимание, вы, возможно, знаете, что у этой операции есть другое название. Это называется картой. Мы сопоставили этот массив с этим массивом. JavaScript позволяет вам делать это конкретное сокращение, просто используя другую функцию. Можно сказать, что var double mapped равно data.map, элемент функции, возвращаемый элемент, умноженный на два. Когда мы запустим это, мы увидим, что получаем то же самое.

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

Точно так же, как когда мы реализовывали map с помощью сокращения, мы собираемся передать пустой массив в качестве значения нашего аккумулятора. Теперь вместо того, чтобы брать каждое значение и помещать его в наш аккумулятор, мы проведем здесь небольшую проверку. Мы собираемся сказать, что если значение по модулю два равно нулю, другими словами, если значение делится на два, то значение accumulator.push возвращает аккумулятор, поэтому Console.log выравнивается. Он выдаст два, четыре и шесть.

Теперь, если вы следите за процессом дома, вы знаете, что название этой операции на самом деле фильтрация. Как и сопоставление выше, JavaScript дает вам возможность фильтровать объект массива. Мы можем сказать, что var даже отфильтровано равно элементу данных функции two.filter. Мы говорим, что возвращаемый элемент по модулю два равен нулю. Если мы логируем даже отфильтрованные, мы увидим, что это работает точно так же. Теперь я знаю, что некоторые из вас сейчас скажут. Вы будете говорить: «Касим, с какой стати мне использовать эту функцию сокращения со всеми этими движущимися частями, когда я могу просто использовать функцию filter или когда я могу просто использовать сопоставить?

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

Сначала фильтруешь. Затем вы просто цепляете карту прямо поверх этого. Если мы сопоставим фильтр console.log, мы получим 8, 4 и 12. Вы совершенно правы. Это совершенно правильный способ думать об этом. Если у вас есть эти инструменты, которые работают так, вы должны использовать их. Я все еще хочу, чтобы вы думали в терминах редукции. Я все еще хочу, чтобы вы поняли, что на самом деле вы сделали только один массив. Но очень важно, чтобы ваш код был простым, лаконичным и читабельным, и я поощряю это. Вот кое-что, о чем вы, возможно, не подумали. Это будет работать нормально. Если вы запускаете это на массиве из шести чисел, делайте это именно так. Что, если у вас есть немного больше информации? А если у вас большой массив?

Давайте создадим миллион элементов в этом массиве. Что, если бы вы захотели выполнить такую ​​же операцию здесь? Вы знаете, ребята, этот классный трюк, когда вы можете сказать console.time? Тогда мы можем сделать ту же операцию. Мы можем сказать, что фильтр отображает большие данные. Это большой data.filter.

Затем, когда мы закончим, мы скажем console.time и большие данные. Мы не хотим здесь выводить миллион элементов в консоль. Но мы собираемся выйти из системы, сколько времени потребуется для запуска этой штуки. Хорошо, это не плохо. 75 миллисекунд.

Посмотри на это. У нас та же логика, но мы будем использовать функцию сокращения. Мы собираемся сказать, что если значение по модулю два равно нулю, то значение accumulator.push, умноженное на два, возвращает аккумулятор. Начнем с пустого массива. Теперь мы собираемся сказать console.time и уменьшить большие данные.

Вау, посмотри на это. Это заняло 79 миллисекунд, это — 54. Почему так? Ну, это потому, что когда вы сочиняете, вы сопоставляете свой фильтр, во-первых, фильтр будет перебирать миллион элементов и возвращать 500 000 элементов. Затем ваша карта должна быть применена к каждому из этих 500 000 элементов.

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

Используйте необязательные аргументы сокращения

Мы знаем, что можно уменьшить массив до другого значения, написав функцию сокращения. Функция сокращения имеет сигнатуру, которая выглядит следующим образом: она принимает аккумулятор, принимает значение и возвращает некоторую интеграцию этого аккумулятора и этого значения, чтобы действовать как новый аккумулятор.
Если у нас есть данные, которые выглядят таким образом, мы можем свести эти данные к сумме, например, с помощью этой функции сокращения. Таким образом, эти числа в сумме составляют 21. Но что, если мы хотим, вместо того, чтобы просто складывать их, что, если мы хотим вычислить среднее значение? Теперь, чтобы сделать это, нам нужно взять сумму, а затем нам нужно разделить сумму на количество элементов в этом массиве.

function reducer(accumulator, value)
return accumulator + value;
}
var data = [1, 2, 3, 3, 4, 5, 3, 1];
var sum = data. reduce (reducer, 0);
console.log(sum / data. length);

Достаточно просто сказать уменьшить на сумму, а затем разделить ее на длину массива. Давайте добавим туда еще одно число, чтобы сделать его не таким чистым. Это работает, верно? Но на самом деле мы не сводили этот массив к среднему значению. Что мы сделали, так это свели его к сумме, а затем вычислили среднее значение.

Как хорошие функциональные программисты, верно, мы хотим думать с точки зрения чистого, одноразового способа преобразовать наши входные данные в то, что мы действительно хотим. Это должно оставить почти немного кода [неразборчиво]. Мы свели его к сумме, и теперь мы прибавляем к этой сумме что-то еще. Есть лучший способ сделать это?

Ответ — да, и способ, которым мы делаем это в JavaScript, заключается в использовании того факта, что на самом деле в функции редуктора есть пара других поддерживаемых аргументов. Первым из этих аргументов является индекс, то есть это индекс в родительском массиве значения, которое оценивается в данный момент. Последний — это сам массив.

Это все одна чистая функция. Если вы пишете модульные тесты, вы можете проверить это. Вы можете передать аккумулятор, значение, некоторый индекс и некоторый массив, и вы можете написать тест, чтобы убедиться, что это ведет себя так, как вы ожидаете, когда вы передаете, например, массив длины 1 и что-то еще. Учитывая, что это чистая функция, это правило говорит вам, как взять некоторый массив чисел и преобразовать его в среднее значение.

Flatten и массивы Flatmap с уменьшением

Давайте поговорим о паре паттернов сокращения, о которых вы, возможно, не обязательно сразу подумаете: «О, это то место, где я должен использовать сокращение». Самый распространенный из них называется flatten. Допустим, у вас есть массив массивов.

Когда вы выполняете for цикл, вы можете отсчитывать назад вместо подсчета вверх. Вы хотите иметь возможность сделать это для уменьшения.

Есть ли способ сделать это? Конечно. Вместо вызова array.reduce вызовите array.reduceRight. Теперь вы можете видеть, что мы ведем обратный отсчет от индекса четыре к трем, к двум, к одному и к нулю. Наш окончательный результат — это то, что происходит, когда вы добавляете строку ноль к строке пять, к строке четыре, к строке три, к строке два к одному.

Составление функций с помощью сокращения

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

«Ну, эй, если у меня есть первоклассные функции в моем языке программирования, как в JavaScript, то на самом деле функции — это просто данные».

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

function increment(input) { return input + 1;}
function decrement(input) { return input - 1; }
function double(input) { return input * 2; }
function halve(input) { return input / 2; }

var initial_value = 1;
var incremented_value = increment(initial_value);
var doubled_value = double(incremented_value);
|var final_value = decrement (doubled_value); I

console.log(final_value);

Теперь предположим, что по какой-то причине у нас есть требование, что мы собираемся получить какое-то входное значение, и нам нужно увеличить это значение, затем нам нужно удвоить это значение, а затем нам нужно уменьшить это значение. Один из способов сделать это — сказать, что наше начальное значение = 1. Наше увеличенное значение = увеличение начального значения. Наше удвоенное значение = удвоенное значение. Наше окончательное значение = уменьшение удвоенного значения.

Если мы скажем console.log final value, мы увидим, что окончательный результат здесь равен 3. Я надеюсь, что это насторожит вас. На самом деле вы бы не хотели делать это таким образом.

Вы инициализируете все эти разные переменные. Вы делаете код более сложным для чтения и вносите ошибки. Что, если я случайно уменьшу свое начальное значение, потому что просто тупо забыл, что я меняю свои переменные или что-то в этом роде?

Это не способ сделать это. Вы думаете: «Хорошо, я хороший программист. Я собираюсь написать единственную функцию, которая улавливает это поведение и не загрязняет пространство кода и мою область действия всеми этими другими переменными».

Вместо того, чтобы делать это таким образом, я слишком умен, чтобы использовать все эти маленькие функции. Я собираюсь написать функцию под названием transform. Он принимает ввод и возвращает (ввод + 1) * 2–1. Я собираюсь сказать, что наше окончательное значение = преобразовать начальное значение. Тот же ответ.

function increment(input) { return input + 1;}
function decrement(input) { return input - 1; }
function double(input) { return input * 2; }
function halve(input) { return input / 2; }

var initial_value = 1;
function transform(input) {
  return ((input + 1) * 2) - 1;
}
var final_value transform(initial_value);

console.log(final_value);

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

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

Конвейер — это термин для серии функций, которые применяются к некоторому начальному значению, чтобы вернуть некоторое конечное значение. Этот конвейер можно вызывать как одну единственную функцию, поэтому у вас нет всех этих промежуточных значений, засоряющих вашу кодовую базу. Конвейеры отлично работают в больших масштабах, Hadoop, Big Data и т. д., но они так же хорошо работают и в простых небольших вещах JavaScript. В нашем небольшом примере здесь вы увидите, что именно это означает.

Поведение, которое мы хотим зафиксировать, заключается в том, что мы хотим увеличить значение. Затем мы хотим удвоить это. Затем мы хотим уменьшить это значение.

Наш конвейер определяется перечислением трех функций в том порядке, в котором мы хотим их использовать. Теперь мы можем сказать, что наше окончательное значение value = pipe.reduce — мы собираемся в конечном итоге уменьшить, правильно — которое берет аккумулятор и функцию и принимает начальное значение.

Помните, как работает сокращение. Аккумулятор — это то, что было возвращено при последнем запуске этой функции, и в данном случае FN. Это значение в нашем массиве. Когда мы вызываем это, FN в первый раз будет увеличиваться, а аккумулятор будет начальным значением. Мы собираемся увеличить начальное значение, равное 1. Это должно дать нам 2. Затем в следующий раз, когда это будет вызвано, аккумулятор будет равен = 2. Функция, мы хотим применить двойную функцию, поэтому это вернет 4. Затем мы собираемся передать 4 в качестве аккумулятора и уменьшить в качестве функции. Чтобы сделать эту работу, это очень просто. Смотреть.

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

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

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

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

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

Я просто хотел бы воспользоваться здесь последней секундой и напомнить вам, что reduceRight также является функцией, и в этом случае вы сначала уменьшите значение вдвое, а затем увеличите, увеличите, удвоите, увеличите, увеличите, увеличите. ReduceRight означает, что вместо того, чтобы начинать с первого значения, начинать с последнего значения и работать в обратном порядке. Если мы уменьшим это вправо, то получим то.