Скорочення Javascript для масиву об'єктів


225

Скажіть, я хочу підсумовувати 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

Що я роблю неправильно в першому прикладі?


1
Також, я думаю, ви маєте arr.reduce(function(a,b){return a + b})на увазі у другому прикладі.
Джеймі Вонг

1
Дякуємо за виправлення. Я натрапив на зменшення тут: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
YXD

6
@Jamie Wong - це насправді частина JavaScript 1.8
JaredMcAteer

@OriginalSyn так - просто це побачив. Цікаво, але оскільки він не має повної натурної підтримки, реалізація все ще має значення при відповіді на подібні запитання.
Джеймі Вонг

3
Версії JavaScript - це лише версії інтерпретатора Firefox, це заплутане посилання на них. Є лише ES3 та ES5.
Райнос

Відповіді:


279

Після першої ітерації ви повертаєте число, а потім намагаєтеся отримати його властивість, 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

Уточнення : я віддаю перевагу моєму методу над іншою верхньою відповіддю в цій темі, оскільки я не згоден з думкою, що передача необов'язкового параметра для зменшення на магічне число, щоб вийти з примітивного номера, є більш чистим. Це може призвести до меншої кількості написаних рядків, але imo є менш читабельним.


7
Звичайно зменшення займає функцію для виконання операцій над кожним з елементів масиву. Кожен раз, коли він повертає значення, яке використовується як наступна змінна 'a' в операції. Отже, перша ітерація a = {x: 1}, b = {x: 2}, потім друга ітерація a = {x: 3} (об'єднане значення першої ітерації), b = {x: 4}. Проблема з вашим прикладом під час другої ітерації намагалася додати 3.x + bx, 3 не має властивості під назвою x, тому він повернувся невизначеним і додав, що bx (4) повернув не число
JaredMcAteer

Я розумію пояснення, але все ще не бачу, як {x: ...} заважає ітерації 2 викликати сокиру? Що означає цей х? Я спробував використовувати цей підхід методом, і, здається, не спрацює
mck

насправді, просто прочитайте stackoverflow.com/questions/2118123/… і зрозумійте, як це працює .. але як би ви це зробили, коли у вас є метод, а не просто прямі атрибути?
mck

278

Більш чіткий спосіб досягти цього - надання початкової вартості:

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.


1
Це рішення, якщо у вас початкове значення (2-й параметр зменшення)
TJ.

Дякую, я шукав підказку з другим аргументом для reduce.
nilsole

Це менш код і прямий, ніж прийнята відповідь - чи є якийсь спосіб, коли прийнята відповідь краща за цю?
jbyrd

1
Це допомогло мені прийняти випадок, коли довжина масиву становить лише 1 іноді.
HussienK

1
es6 way var arr = [{x: 1}, {x: 2}, {x: 4}]; нехай a = arr.reduce ((acc, obj) => acc + obj.x, 0); console.log (a);
amar ghodke

76

TL; DR, встановіть початкове значення

Використання руйнування

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

Без руйнування

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

З Typescript

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 tampered 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порожнього об'єкта. Як це: 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передбачено, то reduceфункція проходить initialItemяк accumulatorі перший елемент масиву, що і itemInArray.

  • Якщо initialItemце не передбачено, reduceфункція передає перший елемент у масиві як initialItemдругий елемент у масиві, itemInArrayщо може заплутати поведінку.

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

Ви можете ознайомитися з документацією за адресою:

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

Сподіваюсь, це допомагає!


1
Руйнування - це саме те, що я хотів. Працює як шарм. Дякую!
ОлександрХ

24

Інші відповіли на це питання, але я подумав, що буду кидати інший підхід. Замість того, щоб безпосередньо перейти до підсумовування сокири, ви можете комбінувати карту (від сокири до х) та зменшити (додати х):

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

Щоправда, це, мабуть, буде трохи повільніше, але я вважав, що варто згадати це як варіант.


8

Для формалізації того, на що вказувалося, редуктор - це катаморфізм, який бере два аргументи, які можуть бути однотипними за збігом обставин, і повертає тип, який відповідає першому аргументу.

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);

1
+1 для чіткого пояснення з точки зору системи типів. ОТО, я думаю, відкриваючи думку про те, що це катаморфізм, можливо, для багатьох ...
Periata Breatta

6

Для першої ітерації 'a' буде першим об’єктом у масиві, отже, ax + bx поверне 1 + 2, тобто 3. Тепер у наступній ітерації повернене 3 присвоюється a, тому a - це число, яке тепер n n називає ax дасть 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)

5

На кожному кроці зменшення ви не повертаєте новий {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}; }) 

3
Перший приклад потребує значення за замовчуванням (наприклад, 0), ще "а" не визначено в першій ітерації.
Гріффін

2

На першому кроці він буде добре працювати, оскільки значення aбуде 1, а значення bбуде 2, але як повернеться 2 + 1, а на наступному кроці значення bбуде повернутим значенням from step 1 i.e 3і такb.x буде невизначено .. .and undefined + anyNumber буде NaN, і саме тому ви отримуєте такий результат.

Натомість ви можете спробувати це, задавши початкове значення як нуль, тобто

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


Оновіть це, щоб показати, чим ваш підхід відрізняється від інших.
kwelch

2

Якщо у вас є складний об'єкт з великою кількістю даних, наприклад масив об'єктів, ви можете скористатися покроковим підходом до вирішення цього питання.

Наприклад:

const myArray = [{ id: 1, value: 10}, { id: 2, value: 20}];

По-перше, ви повинні зіставити масив у новий масив, що цікавить, це може бути новий масив значень у цьому прикладі.

const values = myArray.map(obj => obj.value);

Ця функція зворотного виклику поверне новий масив, що містить лише значення вихідного масиву, і збереже його у значеннях const. Тепер ваші значення const - це такий масив:

values = [10, 20];

І тепер ви готові виконати ваше зниження:

const sum = values.reduce((accumulator, currentValue) => { return accumulator + currentValue; } , 0);

Як бачите, метод скорочення виконує функцію зворотного дзвінка кілька разів. Кожен раз він приймає поточне значення елемента в масиві та підсумовує за допомогою акумулятора. Отже, щоб правильно підсумувати його, потрібно встановити початкове значення вашого акумулятора як другий аргумент методу зменшення.

Тепер у вас є нова сума const зі значенням 30.


0

зменшити функцію ітерації над колекцією

arr = [{x:1},{x:2},{x:4}] // is a collection

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

перекладається на:

arr.reduce(
    //for each index in the collection, this callback function is called
  function (
    a, //a = accumulator ,during each callback , value of accumulator is 
         passed inside the variable "a"
    b, //currentValue , for ex currentValue is {x:1} in 1st callback
    currentIndex,
    array
  ) {
    return a.x + b.x; 
  },
  accumulator // this is returned at the end of arr.reduce call 
    //accumulator = returned value i.e return a.x + b.x  in each callback. 
);

під час кожного зворотного виклику індексу значення змінної «акумулятор» передається параметру «а» у функції зворотного виклику. Якщо ми не ініціалізуємо "акумулятор", його значення буде невизначеним. Виклик undefined.x призведе до помилки.

Щоб вирішити це, ініціалізуйте "акумулятор" зі значенням 0, як показала відповідь Кейсі вище.

Щоб зрозуміти входи функції "зменшити", я б запропонував вам переглянути вихідний код цієї функції. Бібліотека Лодаша має функцію скорочення, яка працює точно так само, як функцію "зменшити" в ES6.

Ось посилання: зменшити вихідний код


0

щоб повернути суму всіх xреквізитів:

arr.reduce(
(a,b) => (a.x || a) + b.x 
)

3
Надання контексту, чому ця відповідь краще, ніж інші прийняті або схвалені, можуть допомогти користувачам шукати рішення свого питання.
Андрій

0

Ви можете використовувати карту та зменшити функції

const arr = [{x:1},{x:2},{x:4}];
const sum = arr.map(n => n.x).reduce((a, b) => a + b, 0);

-1

Функція зменшення масиву приймає три параметри, тобто початкове значення (за замовчуванням це 0), значення акумулятора та поточне значення. За замовчуванням значення InitiValue буде "0". який бере акумулятор

Подивимось це в коді.

var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal ) ; 
// (remember Initialvalue is 0 by default )

//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks .
// solution = 7

Тепер той же приклад із початковим значенням:

var initialValue = 10;
var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal,initialValue ) ; 
/
// (remember Initialvalue is 0 by default but now it's 10 )

//first iteration** : 10 +1 => Now accumulator =11;
//second iteration** : 11 +2 => Now accumulator =13;
//third iteration** : 13 + 4 => Now accumulator = 17;
No more array properties now the loop breaks .
//solution=17

Те саме стосується і об’єктних масивів (поточне запитання stackoverflow):

var arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(acc,currVal){return acc + currVal.x}) 
// destructing {x:1} = currVal;
Now currVal is object which have all the object properties .So now 
currVal.x=>1 
//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks 
//solution=7

ONE THING TO BARE IN MIND є початковим значенням за замовчуванням - 0, і йому можна дати все, що я маю на увазі {}, [] та номер


-1

    
  
 //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))


-2

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

arr.reduce (функція (a, b) {a + bx}, 0) `

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.