Я решил, что у меня будет go глубина, так что у вас будет мыслительный процесс, а не только окончательный код. (Также я многословный ублюдок и люблю писать.: D)
Шаг 1: Рассмотрим общий поток вашей функции
Первое, что нужно рассмотреть, прежде чем даже смотреть на сами данные , смотрит на то, что вы хотите сделать с данными.
Прежде всего, вы хотите l oop через массив, чтобы сделать некоторые вещи с каждым элементом в нем. Это предполагает использование одной из функций, встроенных в массивы, таких как forEach
или map
или reduce
. Все это l oop через элементы массива, передавая каждый элемент определенной вами функции. Но они делают разные вещи с этими данными.
forEach
дает вам доступ к каждому элементу, но не ожидает, что вы что-либо выведете, просто по какой-то причине используя информацию. (Вы можете что-то изменить, но считается хорошей идеей оставить старые данные в покое и создать новую версию, чтобы вы всегда знали, что у вас есть свободный набор данных sh вместо прямой настройки. Если вам нужно чтобы настроить данные, используйте map
, описанный ниже.) map
дает вам доступ к каждому элементу, но ожидает, что вы что-то вернете, чтобы создать новый массив на основе того, что вы получили из старый массив. Так что, если у вас, скажем, есть массив чисел, и вы хотите другой массив, в котором эти числа преобразуются в строки, вы можете создать функцию, такую как (number) => '' + number
, передать ее в map
, и тогда вы получите с новым массивом, в котором все ваши числа преобразованы в строки. reduce
дает вам доступ к каждому элементу, но вместо создания нового массива позволяет объединять информацию, сокращая это в какой-то форме одного значения или объекта. Это метод, который мы собираемся использовать, потому что мы хотим взять элементы в массиве и подключить их к единому объекту, который объединяет информацию.
Мы могли бы использовать for
l oop, чтобы сделать это тоже, но используя встроенные функции, мы можем сосредоточиться только на работе, которая важна для нас - объединение информации - и позволить массиву выполнять служебную работу, проходя по каждому вещь. Это станет более понятным, когда мы соберем все вместе ниже.
Шаг 2: Узнайте, как преобразовать ваши данные
Следующим шагом будет знание того, как вы хотите преобразовать свои данные. Если у вас есть это, вы можете выяснить, как добавить записи вместе.
Если вы посмотрите на свои транзакции, каждая транзакция выглядит следующим образом:
[name, buy or sell, amount]
В конце вы хотите получить объект с ключами и значениями, которые выглядят как
name: [buy amount, sell amount]
Как только вы это увидите, ключ выясняет, как перейти от одной договоренности к другой.
Обработка имени довольно проста. Вы просто передадите его из массива в объект без необходимости его преобразования:
const name = transaction[0];
Следующее, что нужно рассмотреть, - это как разделить суммы покупки и продажи на отдельные поля. Если покупки должны быть по индексу 0, а продажи по индексу 1, это может быть просто:
const amountIndex = transaction[1] === 'buy' ? 0 : 1;
Последний элемент - это фактическая сумма:
const amountValue = transaction[2];
Затем, игнорируя как сложить их вместе, если у нас есть resultsObject
для подключения, мы получим
const name = transaction[0];
const amountIndex = transaction[1] === 'buy' ? 0 : 1;
const amountValue = transaction[2];
// create a new array with starting values
resultsObject[name] = [0, 0];
// put the transaction's value in the right place
resultsObject[name][amountIndex] = amountValue;
Шаг 3: Узнайте, как объединить записи вместе
Есть Несколько вещей, которые мы не рассмотрели на шаге 2:
- Как мы можем создавать новые записи, только если запись еще не существует? (Мы не хотим перезаписывать существующий массив.)
- Как добавить значения в существующий массив?
Это на самом деле просто, но хорошо бы рассмотреть в своем собственном место, потому что без него наши результаты были бы полностью испорчены.
На первый вопрос отвечаем заменой строки создания выше на:
// create a new array with starting values (if it doesn't already exist)
if (! resultsObject[name]) {
resultsObject[name] = [0, 0];
}
На второй вопрос отвечаем выполнением сложения и назначение в следующей строке:
// add the transaction's value to the right place
resultsObject[name][amountIndex] += amountValue; // added the + sign
Если мы соберем это вместе, мы получим основную информацию о том, как мы хотим уменьшить массив в нашем объекте:
const name = transaction[0];
const amountIndex = transaction[1] === 'buy' ? 0 : 1;
const amountValue = transaction[2];
// create a new array with starting values (if it doesn't already exist)
if (! resultsObject[name]) {
resultsObject[name] = [0, 0];
}
// add the transaction's value to the right place
resultsObject[name][amountIndex] += amountValue; // added the + sign
Шаг 4: Создайте функцию, которую мы передаем reduce()
У нас почти полностью встроена функция редуктора . Нам просто нужно добавить определение функции и возвращаемое значение:
const getTransactionSummary = (resultsObject, transaction) => {
const name = transaction[0];
const amountIndex = transaction[1] === 'buy' ? 0 : 1;
const amountValue = transaction[2];
// create a new array with starting values (if it doesn't already exist)
if (! resultsObject[name]) {
resultsObject[name] = [0, 0];
}
// add the transaction's value to the right place
resultsObject[name][amountIndex] += amountValue; // added the + sign
return resultsObject;
}
Функция принимает собираемое нами значение (аккумулятор ) и текущий элемент в массиве и возвращает обновленный аккумулятор.
О любой функции, которую вы используете в reduce
, следует помнить две ключевые вещи:
- Аккумулятор всегда является первым аргументом. Раньше это все время сбрасывало меня, потому что я думал, что элемент массива будет самой важной вещью, поэтому, конечно, он первый! Но нет. Первое значение - это то, которое передается от элемента к элементу, а накапливает данные, когда вы go проходите через массив. Что-то, что нужно отслеживать, когда вы пишете больше редукторов.
- Всегда возвращайте аккумулятор. Он будет передаваться при каждом вызове функции, но если вы не вернете его, следующий элемент получит неопределенный аккумулятор. Если вы получили TypeError в редукторе, убедитесь, что вы возвращаете значение.
Шаг 5: Подключите функцию редуктора к reduce
Последний шаг к получить рабочую сводку транзакций:
const getTransactionSummary = (resultsObject, transaction) => {
const name = transaction[0];
const amountIndex = transaction[1] === 'buy' ? 0 : 1;
const amountValue = transaction[2];
if (! resultsObject[name]) {
resultsObject[name] = [0, 0];
}
resultsObject[name][amountIndex] += amountValue;
return resultsObject;
}
const transactionSummary = transactions.reduce(getTransactionSummary, {});
(я удалил комментарии, чтобы немного его очистить. Сам код довольно понятен, и комментарии разбили поток в конечном продукте.)
Вот и все. Эта последняя строка берет ваш массив (здесь он называется transactions
) и вызывает его функцию reduce
. Мы даем ему ссылку на наш редуктор и начальное значение, которое нужно подключить в качестве результирующего объекта в первый раз. Он возвращает полностью заполненный resultsObject, с транзакциями, объединенными так, как мы хотим. (Если бы мы не включили {}
в качестве начального значения, мы получили бы еще одну ошибку TypeError, когда браузер пожаловался, что у вас не определен аккумулятор.)
Есть три вещи, на которые следует обратить внимание, когда Вы сравниваете это с for
l oop:
- Туго. Нет индекса l oop, о котором нужно беспокоиться, нет условий для проверки, нет дополнительных скобок , Это одна функция и одна строка.
Это декларативно. Вместо того, чтобы читать всю механику зацикливания и агрегирования данных, вы получаете представление о том, что происходит, просто читая слева направо:
- Определите переменную с именем
transactionSummary
- Посмотрите на массив
transactions
reduce
этого массива. .. - ... используя
getTransactionSummary
Определив наш код в функции и добавив эту функцию в качестве аргумента для сокращения, мы можем почти создать предложение из нашего кода. Это может помочь нам улучшить читаемость. В дополнение к тому, что эта строка является краткой, она также упрощает нашу функцию. Функция начинается сверху и работает прямо вниз, с одним условием. Чем меньше условий нам нужно проверить, тем меньше циклов нам нужно отслеживать, тем проще отслеживать, что происходит.
- Это подключаемо. В этом случае мы хотим создать сводку транзакций, поэтому
getTransactionSummary
полезно для сокращения наших транзакций. Если бы мы хотели подсчитать общее количество покупок и продаж, чтобы получить общий объем, мы могли бы создать другую функцию, скажем, getTransactionVolume
. До тех пор, пока он принимает аккумулятор и элемент массива и возвращает обновленный аккумулятор, мы можем заменить getTransactionSummary
на getTransactionVolume
и получить совершенно разные статистические данные из одних и тех же данных.
Эти три элемента (особенно второй и третий) - вот что делает функциональное программирование таким изящным. Существует целый ряд статей о том, что такое функциональное программирование и как оно работает в JavaScript. Веселитесь, гуглив!
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce