Javascript reduzido na matriz de objetos

225

Digamos que eu queira somar a.xpara cada elemento em arr.

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

Eu tenho motivos para acreditar que ax está indefinido em algum momento.

O seguinte funciona bem

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

O que estou fazendo de errado no primeiro exemplo?

YXD
fonte
1
Além disso, acho que você quer dizer arr.reduce(function(a,b){return a + b})no segundo exemplo.
Jamie Wong
1
Obrigado pela correção. Me deparei com reduzir aqui: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
YXD 20/04/11
6
@Jamie Wong é realmente uma parte de JavaScript 1.8
JaredMcAteer
@OriginalSyn sim - acabei de ver isso. Interessante, mas como não possui suporte nativo completo, a implementação ainda é importante ao responder a perguntas como essa.
Jamie Wong
3
Versões JavaScript são apenas versões do interpretador Firefox, é confuso fazer referência a elas. Há apenas ES3 e ES5.
Raynos

Respostas:

279

Após a primeira iteração, você está retornando um número e tentando obter propriedade xdele para adicionar ao próximo objeto, que é a undefinedmatemática que envolve undefinedresultados NaN.

tente retornar um objeto que contenha uma xpropriedade com a soma das propriedades x dos parâmetros:

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}

Explicação adicionada a partir dos comentários:

O valor de retorno de cada iteração [].reduceusada como a avariável na próxima iteração.

Iteração 1: a = {x:1}, b = {x:2}, {x: 3}atribuído a ana iteração 2

Iteração 2: a = {x:3}, b = {x:4}.

O problema com o seu exemplo é que você está retornando um número literal.

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

Iteração 1: a = {x:1}, b = {x:2}, // returns 3como ana próxima iteração

Iteração 2: a = 3, b = {x:2}retornosNaN

Um literal numérico 3não tem (normalmente) uma propriedade chamada, xportanto é undefinede undefined + b.xretorna NaNe NaN + <anything>é sempreNaN

Esclarecimento : Prefiro meu método à outra resposta superior neste tópico, pois discordo da idéia de que passar um parâmetro opcional para reduzir com um número mágico e obter um número primitivo é mais limpo. Isso pode resultar em menos linhas escritas, mas é menos legível.

JaredMcAteer
fonte
7
A redução certa leva em uma função para executar operações em cada um dos elementos em uma matriz. Toda vez que ele retorna um valor que é usado como a próxima variável 'a' na operação. Portanto, a primeira iteração a = {x: 1}, b = {x: 2} e a segunda iteração a = {x: 3} (valor combinado da primeira iteração), b = {x: 4}. O problema com o seu exemplo na segunda iteração foi tentando adicionar 3.x + bx, 3 não têm uma propriedade chamada x por isso voltou indefinido e acrescentando que a bx (4) não retornou um número
JaredMcAteer
Entendo a explicação, mas ainda não vejo como {x: ...} impede a iteração 2 de chamar ax? O que esse x significa? Eu tentei usar essa abordagem com um método, e não parece trabalho
mck
na verdade, basta ler stackoverflow.com/questions/2118123/… e entender como funciona .. mas como você faria isso quando tiver um método, em vez de apenas atributos diretos?
mck
278

Uma maneira mais limpa de fazer isso é fornecendo um valor inicial:

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

A primeira vez que a função anônima é chamada, ela é chamada com (0, {x: 1})e retorna 0 + 1 = 1. Na próxima vez, é chamado com (1, {x: 2})e retorna 1 + 2 = 3. É então chamado com (3, {x: 4}), finalmente retornando 7.

Casey Chu
fonte
1
Esta é a solução se você tiver um valor inicial (2º parâmetro de redução)
TJ.
Thanx, eu estava procurando a dica com o segundo argumento para reduce.
N
Isso é menos código e mais direto que a resposta aceita - existe alguma maneira pela qual a resposta aceita seja melhor do que isso?
jbyrd
1
Isso me ajudou a entender o caso quando o comprimento da matriz é apenas 1 às vezes.
HussienK
1
es6 way var arr = [{x: 1}, {x: 2}, {x: 4}]; deixe a = arr.reduce ((acc, obj) => acc + obj.x, 0); console.log (a);
amar ghodke
76

TL; DR, defina o valor inicial

Usando desestruturação

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

Sem desestruturação

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

Com texto datilografado

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

Vamos tentar o método de desestruturação:

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

A chave para isso é definir o valor inicial. O valor de retorno se torna o primeiro parâmetro da próxima iteração.

A técnica usada na resposta principal não é idiomática

A resposta aceita propõe NÃO passar o valor "opcional". Isso está errado, pois a maneira idiomática é que o segundo parâmetro seja sempre incluído. Por quê? Três razões:

1. Perigoso - Não passar o valor inicial é perigoso e pode criar efeitos colaterais e mutações se a função de retorno de chamada for descuidada.

Ver

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

Se, no entanto, tivéssemos feito dessa maneira, com o valor inicial:

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

Para o registro, a menos que você pretenda alterar o objeto original, defina o primeiro parâmetro de Object.assigncomo um objeto vazio. Como esta: Object.assign({}, a, b, c).

2 - Melhor Inferência de Tipo usar uma ferramenta como Typescript ou um editor como o VS Code, você obtém o benefício de informar ao compilador a inicial e ele pode detectar erros se você estiver fazendo errado. Se você não definir o valor inicial, em muitas situações, talvez não seja possível adivinhar e você poderá acabar com erros de tempo de execução assustadores.

3 - Respeite os Functors - JavaScript brilha melhor quando seu filho funcional interno é liberado. No mundo funcional, há um padrão sobre como você "dobra" ou reduceuma matriz. Quando você dobra ou aplica um catamorfismo à matriz, utiliza os valores dessa matriz para construir um novo tipo. Você precisa comunicar o tipo resultante - você deve fazer isso mesmo que o tipo final seja o dos valores na matriz, outra matriz ou qualquer outro tipo.

Vamos pensar de outra maneira. No JavaScript, as funções podem ser transmitidas como dados; é assim que os retornos de chamada funcionam; qual é o resultado do código a seguir?

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

Ele retornará um número? Um objeto? Isso torna mais claro

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

Leia mais sobre a especificação de programação funcional aqui: https://github.com/fantasyland/fantasy-land#foldable

Um pouco mais de experiência

O reducemétodo usa dois parâmetros,

Array.prototype.reduce( callback, initialItem )

A callbackfunção aceita os seguintes parâmetros

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

Para a primeira iteração,

  • Se initialItemfor fornecida, a reducefunção passa o initialItemcomo o accumulatore o primeiro item da matriz como o itemInArray.

  • Se nãoinitialItem for fornecida, a função passa o primeiro item da matriz como o e o segundo item da matriz, o que pode ser um comportamento confuso.reduceinitialItemitemInArray

Eu ensino e recomendo sempre definir o valor inicial de reduzir.

Você pode verificar a documentação em:

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

Espero que isto ajude!

Babakness
fonte
1
Desestruturar é exatamente o que eu queria. Funciona como um encanto. Obrigado!
AleksandrH
24

Outros responderam a essa pergunta, mas pensei em lançar outra abordagem. Em vez de ir diretamente para a soma do machado, você pode combinar um mapa (de machado para x) e reduzir (para adicionar x):

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

É certo que provavelmente será um pouco mais lento, mas achei que vale a pena mencionar como uma opção.

RHSeeger
fonte
8

Para formalizar o que foi apontado, um redutor é um catamorfismo que pega dois argumentos que podem ser do mesmo tipo por coincidência e retorna um tipo que corresponde ao primeiro argumento.

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

Isso significa que o corpo do redutor precisa ser sobre a conversão currentValuee o valor atual do accumulatorpara o valor do novo accumulator.

Isso funciona de maneira direta ao adicionar, porque os valores do acumulador e do elemento são do mesmo tipo (mas servem a propósitos diferentes).

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

Isso só funciona porque são todos números.

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

Agora estamos dando um valor inicial ao agregador. O valor inicial deve ser o tipo que você espera que o agregador (o tipo que você espera que seja o valor final), na grande maioria dos casos. Enquanto você não é forçado a fazer isso (e não deveria ser), é importante ter em mente.

Depois que você souber disso, poderá escrever reduções significativas para outros problemas de relacionamento n: 1.

Removendo palavras repetidas:

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

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

Fornecendo uma contagem de todas as palavras encontradas:

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

Reduzindo uma matriz de matrizes para uma única matriz plana:

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

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

Sempre que você deseja passar de uma variedade de coisas para um único valor que não corresponde a 1: 1, reduzir é algo que você pode considerar.

De fato, o mapa e o filtro podem ser implementados como reduções:

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

Espero que isso forneça algum contexto adicional de como usar reduce.

A única adição a isso, que ainda não expliquei, é quando existe uma expectativa de que os tipos de entrada e saída sejam especificamente dinâmicos, porque os elementos da matriz são funções:

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);
Norguard
fonte
1
+1 por explicá-lo claramente em termos do sistema de tipos. OTOH, eu acho que por abertura com a idéia de que é um catamorphism podem ser offputting para muitos ...
Periata Breatta
6

Para a primeira iteração 'a' será o primeiro objeto na matriz, portanto ax + bx retornará 1 + 2 ou seja, 3. Agora, na próxima iteração, o 3 retornado é atribuído a a, então a é um número que está chamando ax vai dar NaN.

A solução simples é primeiro mapear os números na matriz e depois reduzi-los como abaixo:

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

aqui arr.map(a=>a.x)irá fornecer uma matriz de números [1,2,4] agora usando .reduce(function(a,b){return a+b})irá simplesmente adicionar esses números sem nenhum hassel

Outra solução simples é apenas fornecer uma soma inicial como zero atribuindo 0 a 'a' como abaixo:

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

Em cada etapa da sua redução, você não está retornando um novo {x:???}objeto. Então você precisa:

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

ou você precisa fazer

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; }) 
Jamie Wong
fonte
3
O primeiro exemplo precisa de um valor padrão (como 0), caso contrário "a" é indefinido na primeira iteração.
Griffin
2

Na primeira etapa, funcionará bem, pois o valor de aserá 1 e o de bserá 2, mas como 2 + 1 será retornado e na próxima etapa o valor de bserá o valor de retorno from step 1 i.e 3e, portanto b.x, será indefinido. .e undefined + anyNumber será NaN e é por isso que você está obtendo esse resultado.

Em vez disso, você pode tentar isso dando o valor inicial como zero, ou seja,

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

Nitesh Ranjan
fonte
Atualize isso para mostrar como sua abordagem é diferente das outras.
Kwelch
2

Se você possui um objeto complexo com muitos dados, como uma matriz de objetos, pode seguir uma abordagem passo a passo para resolver isso.

Por exemplo:

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

Primeiro, você deve mapear sua matriz para uma nova matriz de seu interesse; pode ser uma nova matriz de valores neste exemplo.

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

Essa função de retorno de chamada retornará uma nova matriz contendo apenas valores da matriz original e a armazenará nos valores const. Agora seus valores const são uma matriz como esta:

values = [10, 20];

E agora você está pronto para executar sua redução:

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

Como você pode ver, o método reduzir executa a função de retorno de chamada várias vezes. Para cada vez, ele pega o valor atual do item na matriz e soma com o acumulador. Portanto, para somar corretamente, você precisa definir o valor inicial do seu acumulador como o segundo argumento do método de redução.

Agora você tem sua nova const sum com o valor de 30.

João Ramires
fonte
0

a função reduzir itera sobre uma coleção

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

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

traduz para:

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

durante cada retorno de chamada do índice, o valor da variável "acumulador" é passado para o parâmetro "a" na função de retorno de chamada. Se não inicializarmos o "acumulador", seu valor será indefinido. Chamar undefined.x causaria um erro.

Para resolver isso, inicialize o "acumulador" com o valor 0, como a resposta de Casey mostrou acima.

Para entender as entradas e saídas da função "reduzir", sugiro que você analise o código-fonte dessa função. A biblioteca Lodash possui a função reduzir, que funciona exatamente da mesma forma que a função "reduzir" no ES6.

Aqui está o link: reduza o código fonte

Deen John
fonte
0

para retornar uma soma de todos os xadereços:

arr.reduce(
(a,b) => (a.x || a) + b.x 
)
Black Jack
fonte
3
Fornecer um contexto sobre por que essa resposta é melhor que as outras aceitas ou votadas pode ajudar os usuários a procurar a solução para sua pergunta.
Andrew Andrew
0

Você pode usar o mapa e reduzir funções

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

A função de redução de matriz leva três parâmetros, ou seja, valor inicial (o padrão é 0), acumulador e valor atual. Por padrão, o valor de initialValue será "0". que é tomado pelo acumulador

Vamos ver isso no código.

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

Agora mesmo exemplo com o valor inicial:

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

O mesmo se aplica às matrizes de objetos (a questão atual do 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

Uma coisa a se desdobrar é InitialValue, por padrão, é 0 e pode receber qualquer coisa que eu queira dizer {}, [] e number

M.Dhee
fonte
-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))

Rajesh Bose
fonte
-2

você não deve usar axe como acumulador. Em vez disso, você pode fazer assim `arr = [{x: 1}, {x: 2}, {x: 4}]

arr.reduce (função (a, b) {a + bx}, 0) `

RAks BM
fonte