Javascript уменьшить на массив объектов - PullRequest
171 голосов
/ 20 апреля 2011

Скажем, я хочу суммировать a.x для каждого элемента в arr.

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a.x + b.x})
>> NaN

У меня есть основания полагать, что топор не определен в какой-то момент.

Следующие работыотлично

arr = [1,2,4]
arr.reduce(function(a,b){return a + b})
>> 7

Что я делаю не так в первом примере?

Ответы [ 12 ]

222 голосов
/ 20 апреля 2011

После первой итерации вы возвращаете число, а затем пытаетесь получить свойство x для его добавления к следующему объекту, который является undefined, а математика с undefined приводит к NaN.

попробуйте вернуть объект, содержащий свойство x с суммой свойств x параметров:

var arr = [{x:1},{x:2},{x:4}];

arr.reduce(function (a, b) {
  return {x: a.x + b.x}; // returns object with property x
})

// ES6
arr.reduce((a, b) => ({x: a.x + b.x}));

// -> {x: 7}

Объяснение добавлено из комментариев:

Возвращаемое значение каждой итерации [].reduce, используемое в качестве переменной a на следующей итерации.

Итерация 1: a = {x:1}, b = {x:2}, {x: 3} присваивается a в Итерации 2

Итерация 2: a = {x:3}, b = {x:4}.

Проблема вашего примера в том, что вы возвращаете числовой литерал.

function (a, b) {
  return a.x + b.x; // returns number literal
}

Итерация 1: a = {x:1}, b = {x:2}, // returns 3 как a в следующей итерации

Итерация 2: a = 3, b = {x:2} возвращает NaN

Числовой литерал 3 (обычно) не имеет свойства с именем x, поэтому undefined и undefined + b.x возвращает NaN, а NaN + <anything> всегда NaN

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

215 голосов
/ 10 июня 2011

Более чистый способ сделать это, указав начальное значение:

var arr = [{x:1}, {x:2}, {x:4}];
arr.reduce(function (acc, obj) { return acc + obj.x; }, 0); // 7
console.log(arr);

При первом вызове анонимной функции она вызывается с (0, {x: 1}) и возвращает 0 + 1 = 1. В следующий раз он вызывается с (1, {x: 2}) и возвращает 1 + 2 = 3. Затем он вызывается с (3, {x: 4}), наконец, возвращая 7.

36 голосов
/ 19 августа 2016

TL; DR, установите начальное значение

Используя Разрушение

arr.reduce( ( sum, { x } ) => sum + x , 0)

Без разрушения

arr.reduce( ( sum , cur ) => sum + cur.x , 0)

С машинописным шрифтом

arr.reduce( ( sum, { x } : { x: number } ) => sum + x , 0)

Давайте попробуем метод деструктуризации:

const arr = [ { x: 1 }, { x: 2 }, { x: 4 } ]
const result = arr.reduce( ( sum, { x } ) => sum + x , 0)
console.log( result ) // 7

Ключом к этому является установка начального значения.Возвращаемое значение становится первым параметром следующей итерации.

Техника, используемая в верхнем ответе, не является идиоматической

В принятом ответе предлагается НЕ передавать «необязательное» значение.Это неправильно, так как идиоматический способ заключается в том, что второй параметр всегда должен быть включен.Зачем?Три причины:

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

Вот

const badCallback = (a,i) => Object.assign(a,i) 

const foo = [ { a: 1 }, { b: 2 }, { c: 3 } ]
const bar = foo.reduce( badCallback )  // bad use of Object.assign
// Look, we've tempered with the original array
foo //  [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]

Если, однако, мы сделали это таким образом, с начальным значением:

const bar = foo.reduce( badCallback, {})
// foo is still OK
foo // {a:1,b:2,c:3}

Для записи всегда присваивайте это Object.assign({}, a, b, c),Установите первый параметр для пустого объекта {}

2 - лучший вывод типа - При использовании инструмента, такого как Typescript, или редактора, такого как VS Code, вы получаете возможность сообщитькомпилятор инициала, и он может отлавливать ошибки, если вы делаете это неправильно.Если вы не установите начальное значение, во многих ситуациях его невозможно будет угадать, и вы можете получить жуткие ошибки времени выполнения.

3 - Уважение к функторам - JavaScript светит лучше, когда его внутренний функциональный дочерний элемент освобожден.В функциональном мире существует стандарт того, как вы «складываете» или reduce массив.Когда вы складываете или применяете катаморфизм к массиву, вы берете значения этого массива для создания нового типа.Вам нужно сообщить результирующий тип - вы должны сделать это, даже если последний тип - это тип значений в массиве, другом массиве или любом другом типе.

Давайте подумаем об этом по-другому.В JavaScript функции могут передаваться как данные, как работают обратные вызовы, каков результат следующего кода?

[1,2,3].reduce(callback)

Будет ли возвращаться число?Объект?Это делает его более понятным

[1,2,3].reduce(callback,0)

Подробнее о спецификациях функционального программирования здесь: https://github.com/fantasyland/fantasy-land#foldable

Еще немного фона

* reduceметод принимает два параметра:

Array.prototype.reduce( callback, initialItem )

Функция callback принимает следующие параметры

(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }

Когда предоставляется initialItem, он передается как accumulator в *Функция 1087 * в первом прошлом, а itemInArray - первый элемент в массиве.

Если initialItem не установлен, reduce принимает первый элемент в массиве как initialItem и передаетвторой пункт как itemInArray.Это может сбивать с толку поведение.

Я учу и рекомендую всегда устанавливать начальное значение снижения.

Полный текст статьи по Array.prototype.reduce см .:

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

Надеюсь, это поможет!

22 голосов
/ 21 апреля 2011

Другие ответили на этот вопрос, но я решил бросить другой подход.Вместо того, чтобы перейти непосредственно к суммированию топора, вы можете объединить карту (от топора до x) и уменьшить (добавить х):

arr = [{x:1},{x:2},{x:4}]
arr.map(function(a) {return a.x;})
   .reduce(function(a,b) {return a + b;});

По общему признанию, это, вероятно, будет немного медленнее, но я подумалэто стоит упомянуть как вариант.

7 голосов
/ 12 ноября 2016

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

function reducer (accumulator: X, currentValue: Y): X { }

Это означает, что тело редуктора должно быть примерно преобразовано currentValue и текущее значение accumulator в значение нового accumulator.

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

[1, 2, 3].reduce((x, y) => x + y);

Это просто работает, потому что они все числа.

[{ age: 5 }, { age: 2 }, { age: 8 }]
  .reduce((total, thing) => total + thing.age, 0);

Теперь мы даем начальное значение агрегатору. Начальным значением должен быть тип, который, как вы ожидаете, будет агрегатором (тип, который вы ожидаете получить в качестве конечного значения), в подавляющем большинстве случаев. Хотя вы не обязаны это делать (и не должны этого делать), важно помнить.

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

Удаление повторяющихся слов:

const skipIfAlreadyFound = (words, word) => words.includes(word)
    ? words
    : words.concat(word);

const deduplicatedWords = aBunchOfWords.reduce(skipIfAlreadyFound, []);

Подсчет всех найденных слов:

const incrementWordCount = (counts, word) => {
  counts[word] = (counts[word] || 0) + 1;
  return counts;
};
const wordCounts = words.reduce(incrementWordCount, { });

Сокращение массива массивов до одного плоского массива:

const concat = (a, b) => a.concat(b);

const numbers = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
].reduce(concat, []);

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

Фактически, карта и фильтр могут быть реализованы как сокращения:

const map = (transform, array) =>
  array.reduce((list, el) => list.concat(transform(el)), []);

const filter = (predicate, array) => array.reduce(
  (list, el) => predicate(el) ? list.concat(el) : list,
  []
);

Надеюсь, это даст дополнительный контекст для использования reduce.

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

const compose = (...fns) => x =>
  fns.reduceRight((x, f) => f(x), x);

const hgfx = h(g(f(x)));
const hgf = compose(h, g, f);
const hgfy = hgf(y);
const hgfz = hgf(z);
5 голосов
/ 11 апреля 2019

Для первой итерации 'a' будет первым объектом в массиве, следовательно, ax + bx вернет 1 + 2, т. Е. 3. Теперь на следующей итерации возвращенные 3 присваиваются a, поэтому a теперь является числомn вызов топора даст NaN.

Простое решение - сначала отобразить числа в массиве, а затем уменьшить их, как показано ниже:

arr.map(a=>a.x).reduce(function(a,b){return a+b})

здесь arr.map(a=>a.x) предоставит массив чисел [1, 2,4] теперь, используя .reduce(function(a,b){return a+b}), вы просто добавите эти числа без каких-либо проблем

Другое простое решение - просто предоставить начальную сумму как ноль, присваивая 0 'a', как показано ниже:

arr.reduce(function(a,b){return a + b.x},0)
4 голосов
/ 20 апреля 2011

На каждом шаге сокращения вы не возвращаете новый {x:???} объект. Так что вам либо нужно сделать:

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a + b.x})

или вам нужно сделать

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; }) 
2 голосов
/ 13 декабря 2017

На первом шаге он будет работать нормально, так как значение a будет равно 1, а значение b будет равно 2, но как 2 + 1 будет возвращено, а на следующем шаге значение b будет возвращаемое значение from step 1 i.e 3, и поэтому b.x будет неопределенным ... и неопределенным + anyNumber будет NaN, и именно поэтому вы получаете этот результат.

Вместо этого вы можете попробовать это, задав начальное значение как ноль, т.е.

arr.reduce(function(a,b){return a + b.x},0);

0 голосов
/ 12 июня 2019

вы не должны использовать .x для аккумулятора, вместо этого вы можете сделать это `arr = [{x: 1}, {x: 2}, {x: 4}]

arr.reduce (function (a, b) {a + b.x}, 0) `

0 голосов
/ 25 марта 2019

    
  
 //fill creates array with n element
 //reduce requires 2 parameter , 3rd parameter as a length
 var fibonacci = (n) => Array(n).fill().reduce((a, b, c) => {
      return a.concat(c < 2 ? c : a[c - 1] + a[c - 2])
  }, [])
  console.log(fibonacci(8))
...