JavaScript zmniejsza liczbę obiektów


225

Powiedzmy, że chcę sumować a.xdla każdego elementu arr.

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

Mam powody sądzić, że topór jest w pewnym momencie niezdefiniowany.

Następujące działa dobrze

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

Co robię źle w pierwszym przykładzie?


1
Myślę też, że masz na myśli arr.reduce(function(a,b){return a + b})drugi przykład.
Jamie Wong,

1
Dziękuję za poprawienie mnie. Natknąłem się na redukcję tutaj: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
YXD

6
@Jamie Wong, tak naprawdę jest częścią JavaScript 1.8
JaredMcAteer

@OriginalSyn tak - właśnie to widziałem. Ciekawe, ale ponieważ nie ma pełnego natywnego wsparcia, implementacja nadal ma znaczenie przy odpowiadaniu na takie pytania.
Jamie Wong,

3
Wersje JavaScript to tylko wersje interpretera Firefoksa, mylące jest odwoływanie się do nich. Jest tylko ES3 i ES5.
Raynos,

Odpowiedzi:


279

Po pierwszej iteracji zwracasz liczbę, a następnie próbujesz uzyskać jej właściwość x, aby dodać ją do następnego obiektu, który obejmuje undefinedmatematykę i undefinedwyniki NaN.

spróbuj zwrócić obiekt zawierający xwłaściwość z sumą x właściwości parametrów:

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}

Wyjaśnienie dodano z komentarzy:

Zwracana wartość każdej iteracji [].reduceużywana jako azmienna w następnej iteracji.

Iteracji 1: a = {x:1}, b = {x:2}, {x: 3}przypisany aw iteracji 2

Iteracja 2: a = {x:3}, b = {x:4}.

Problem z twoim przykładem polega na tym, że zwracasz literał liczbowy.

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

Iteracja 1: a = {x:1}, b = {x:2}, // returns 3jak aw następnej iteracji

Iteracja 2: a = 3, b = {x:2}powracaNaN

Szereg dosłowny 3nie (zazwyczaj) mają właściwość o nazwie xwięc undefinedi undefined + b.xpowraca NaNi NaN + <anything>jest zawszeNaN

Wyjaśnienie : Wolę moją metodę niż drugą najwyższą odpowiedź w tym wątku, ponieważ nie zgadzam się z pomysłem, że przekazanie opcjonalnego parametru zmniejszania za pomocą magicznej liczby w celu uzyskania liczby pierwotnej jest czystsze. Może to skutkować zapisaniem mniejszej liczby wierszy, ale imo jest mniej czytelne.


7
Pewnie redukcja przyjmuje funkcję wykonywania operacji na każdym z elementów tablicy. Za każdym razem zwraca wartość, która jest używana jako następna zmienna „a” w operacji. Więc pierwsza iteracja a = {x: 1}, b = {x: 2}, a następnie druga iteracja a = {x: 3} (łączna wartość pierwszej iteracji), b = {x: 4}. Problem z Twojego przykład w drugiej iteracji został próbuje dodać 3.x + bx, 3 nie posiada właściwość o nazwie X więc powrócił niezdefiniowane i dodając, że BX (4) powrócił Not a Number
JaredMcAteer

Rozumiem wyjaśnienie, ale wciąż nie rozumiem, w jaki sposób {x: ...} uniemożliwia iteracji 2 wywołanie topora? Co oznacza to x? Próbowałem użyć tego podejścia z metodą i wydaje się, że nie działa
mck

właściwie po prostu przeczytaj stackoverflow.com/questions/2118123/... i zrozum, jak to działa .. ale jak byś to zrobił, gdy masz metodę, a nie tylko proste atrybuty?
od

278

Lepszym sposobem na osiągnięcie tego jest podanie wartości początkowej:

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

Przy pierwszym wywołaniu funkcji anonimowej zostaje wywołana za pomocą (0, {x: 1})i zwraca 0 + 1 = 1. Następnym razem zostaje wywołany (1, {x: 2})i wraca 1 + 2 = 3. Następnie jest wywoływany (3, {x: 4}), w końcu wraca 7.


1
Jest to rozwiązanie, jeśli masz wartość początkową (2. parametr redukcji)
TJ.

Dzięki szukałem podpowiedzi z drugim argumentem za reduce.
nilsole

Jest to mniej kodu i bardziej bezpośrednie niż zaakceptowana odpowiedź - czy jest jakiś sposób, aby zaakceptowana odpowiedź była lepsza?
jbyrd

1
Pomogło mi to wziąć pod uwagę przypadek, gdy długość tablicy wynosi czasami tylko 1.
HussienK

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

76

TL; DR, ustaw wartość początkową

Wykorzystanie destrukcji

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

Bez destrukcji

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

Z maszynopisu

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

Spróbujmy metody destrukcji:

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

Kluczem do tego jest ustawienie wartości początkowej. Zwracana wartość staje się pierwszym parametrem następnej iteracji.

Technika zastosowana w górnej odpowiedzi nie jest idiomatyczna

Przyjęta odpowiedź proponuje NIE przekazywanie wartości „opcjonalnej”. Jest to błędne, ponieważ idiomatyczny sposób polega na tym, że zawsze dołączany jest drugi parametr . Czemu? Trzy powody:

1. Niebezpieczne - nieprzekazanie wartości początkowej jest niebezpieczne i może powodować skutki uboczne i mutacje, jeśli funkcja oddzwaniania jest nieostrożna.

Ujrzeć

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 } ]

Gdybyśmy jednak zrobili to w ten sposób, z wartością początkową:

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

Dla potrzeb rekordu, chyba że masz zamiar zmutować oryginalny obiekt, ustaw pierwszy parametr Object.assignna pusty obiekt. Tak: Object.assign({}, a, b, c).

2 - Lepsze wnioskowanie o typach - podczas korzystania z narzędzia takiego jak Typescript lub edytora, takiego jak VS Code, zyskujesz na informowaniu kompilatora o inicjałach i może on wychwytywać błędy, jeśli popełnisz błąd. Jeśli nie ustawisz wartości początkowej, w wielu sytuacjach może nie być w stanie zgadnąć, co może spowodować przerażające błędy w czasie wykonywania.

3 - Respect the Functors - JavaScript świeci najlepiej, gdy jego wewnętrzne funkcjonalne dziecko jest uruchomione. W świecie funkcjonalnym istnieje standard określający sposób „składania” lub reducemacierzy. Kiedy spasujesz lub zastosujesz katamorfizm do tablicy, bierzesz wartości tej tablicy, aby skonstruować nowy typ. Musisz przekazać wynikowy typ - powinieneś to zrobić, nawet jeśli końcowym typem są wartości z tablicy, innej tablicy lub dowolnego innego typu.

Pomyślmy o tym w inny sposób. W JavaScript funkcje mogą być przekazywane jak dane, tak działają wywołania zwrotne, co jest wynikiem następującego kodu?

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

Czy zwróci liczbę? Obiekt? To czyni to jaśniejszym

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

Przeczytaj więcej na temat specyfikacji programowania funkcjonalnego tutaj: https://github.com/fantasyland/fantasy-land#foldable

Trochę więcej tła

reduceSposób dwa parametry,

Array.prototype.reduce( callback, initialItem )

callbackFunkcja przyjmuje następujące parametry

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

W pierwszej iteracji

  • Jeśli initialItempodano, reducefunkcja przekazuje initialItemjako accumulatori pierwszy element tablicy jako itemInArray.

  • Jeśli nieinitialItem jest podana, funkcja przekazuje pierwszy element w tablicy jako drugi element w tablicy, co może być mylące.reduceinitialItemitemInArray

Uczę i zalecam zawsze ustawianie początkowej wartości redukcji.

Możesz sprawdzić dokumentację na:

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

Mam nadzieję że to pomoże!


1
Destrukcja jest tym, czego chciałem. Działa jak marzenie. Dziękuję Ci!
AleksandrH,

24

Inni odpowiedzieli na to pytanie, ale myślałem, że rzuciłbym inne podejście. Zamiast przejść bezpośrednio do sumowania siekiery, możesz połączyć mapę (od siekiery do x) i zmniejszyć (aby dodać x):

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

Wprawdzie prawdopodobnie będzie nieco wolniej, ale pomyślałem, że warto wspomnieć o tym jako opcji.


8

Aby sformalizować to, co zostało wskazane, reduktor jest katamorfizmem, który przyjmuje dwa argumenty, które mogą być tego samego typu przez przypadek, i zwraca typ, który pasuje do pierwszego argumentu.

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

Oznacza to, że korpus reduktora musi polegać na konwersji currentValuei bieżącej wartości accumulatorna wartość nowego accumulator.

Działa to w prosty sposób podczas dodawania, ponieważ zarówno akumulator, jak i wartości elementu są tego samego typu (ale służą innym celom).

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

To po prostu działa, ponieważ wszystkie są liczbami.

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

Teraz podajemy wartość początkową agregatorowi. Wartością początkową powinien być typ, jakiego oczekuje się od agregatora (typ, który ma się pojawić jako wartość końcowa), w zdecydowanej większości przypadków. Chociaż nie musisz tego robić (i nie powinno tak być), należy o tym pamiętać.

Kiedy już to wiesz, możesz napisać znaczące redukcje dla innych problemów w relacji n: 1.

Usuwanie powtarzających się słów:

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

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

Podając liczbę wszystkich znalezionych słów:

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

Zmniejszenie tablicy tablic do pojedynczej płaskiej tablicy:

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

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

Za każdym razem, gdy chcesz przejść od szeregu rzeczy do pojedynczej wartości, która nie pasuje do 1: 1, warto rozważyć redukcję.

W rzeczywistości mapę i filtr można wdrożyć jako redukcje:

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,
  []
);

Mam nadzieję, że zapewnia to dodatkowy kontekst użycia reduce.

Jedynym dodatkiem do tego, do czego jeszcze się nie włamałem, jest oczekiwanie, że typy wejściowe i wyjściowe mają być dynamiczne, ponieważ elementy tablicy są funkcjami:

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 za jasne wyjaśnienie tego w kategoriach systemu typów. OTOH, myślę, że otwierając od pomysłu, że jest to katamorfizm, może być szkodliwy dla wielu ...
Periata Breatta

6

Dla pierwszej iteracji „a” będzie pierwszym obiektem w tablicy, stąd ax + bx zwróci 1 + 2, tj. 3. Teraz w następnej iteracji zwrócone 3 jest przypisane do a, a więc a jest liczbą teraz n nazywającą siekierę da NaN.

Prostym rozwiązaniem jest najpierw mapowanie liczb w tablicy, a następnie ich zmniejszenie, jak poniżej:

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

tutaj arr.map(a=>a.x)zapewni tablicę liczb [1,2,4]. Teraz .reduce(function(a,b){return a+b})wystarczy użyć tych liczb bez żadnego hassel

Innym prostym rozwiązaniem jest podanie początkowej sumy jako zero poprzez przypisanie 0 do „a” jak poniżej:

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

5

Na każdym etapie redukcji nie zwracasz nowego {x:???}obiektu. Więc albo musisz zrobić:

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

lub musisz to zrobić

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

3
Pierwszy przykład wymaga wartości domyślnej (takiej jak 0), w przeciwnym razie „a” jest niezdefiniowane w pierwszej iteracji.
Griffin

2

W pierwszym kroku będzie działać dobrze, ponieważ wartość abędzie wynosić 1, a wartość bbędzie wynosić 2, ale jako 2 + 1 zostanie zwrócona, aw następnym kroku wartość bbędzie wartością zwracaną, from step 1 i.e 3a zatem b.xbędzie niezdefiniowana. .i undefined + anyNumber będzie NaN i dlatego otrzymujesz ten wynik.

Zamiast tego możesz spróbować, podając wartość początkową jako zero, tj

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


Zaktualizuj to, aby pokazać, jak Twoje podejście różni się od innych.
kwelch

2

Jeśli masz złożony obiekt z dużą ilością danych, taki jak tablica obiektów, możesz rozwiązać problem krok po kroku.

Na przykład:

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

Najpierw powinieneś zmapować tablicę na nową tablicę, która Cię interesuje, w tym przykładzie może to być nowa tablica wartości.

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

Ta funkcja zwrotna zwróci nową tablicę zawierającą tylko wartości z oryginalnej tablicy i zapisze ją w wartości const. Teraz twoje wartości const są tablicą podobną do tej:

values = [10, 20];

A teraz jesteś gotowy, aby wykonać redukcję:

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

Jak widać, metoda zmniejszająca wykonuje funkcję wywołania zwrotnego wiele razy. Za każdym razem pobiera bieżącą wartość elementu z tablicy i sumuje z akumulatorem. Aby właściwie to zsumować, musisz ustawić wartość początkową akumulatora jako drugi argument metody redukcji.

Teraz masz nową stałą sumę o wartości 30.


0

zredukuj iteracje funkcji po kolekcji

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

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

przetłumaczyć na:

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

podczas każdego wywołania zwrotnego wartość zmiennej „akumulator” jest przekazywana do parametru „a” w funkcji wywołania zwrotnego. Jeśli nie zainicjujemy „akumulatora”, jego wartość będzie niezdefiniowana. Wywołanie undefined.x spowoduje błąd.

Aby rozwiązać ten problem, zainicjuj „akumulator” wartością 0, tak jak pokazano powyżej odpowiedź Casey.

Aby zrozumieć wejścia funkcji „zmniejsz”, sugeruję zajrzeć do kodu źródłowego tej funkcji. Biblioteka Lodash ma funkcję zmniejszania, która działa dokładnie tak samo jak funkcja „zmniejsz” w ES6.

Oto link: zmniejsz kod źródłowy


0

aby zwrócić sumę wszystkich xrekwizytów:

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

3
Podanie kontekstu, dlaczego ta odpowiedź jest lepsza od innych zaakceptowanych lub ocenionych, może pomóc użytkownikom w szukaniu rozwiązania ich pytania.
Andrew

0

Możesz użyć mapy i ograniczyć funkcje

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

-1

Funkcja redukcji tablic przyjmuje trzy parametry, tj. Wartość początkową (domyślnie jest to 0), akumulator i wartość prądu. Domyślnie wartość initialValue będzie wynosić „0”. który jest pobierany przez akumulator

Zobaczmy to w kodzie.

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

Teraz ten sam przykład z wartością początkową:

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

To samo dotyczy również tablic obiektowych (aktualne pytanie dotyczące przepływu stosu):

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

JEDNA RZECZ, KTÓRA NADUŻYĆ W UMYSŁU, domyślnie jest to wartość początkowa 0 i można podać wszystko, co mam na myśli {}, [] i liczbę


-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

nie powinieneś używać siekiery jako akumulatora, zamiast tego możesz zrobić tak: `arr = [{x: 1}, {x: 2}, {x: 4}]

arr.reduce (funkcja (a, b) {a + bx}, 0) `

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.