evsc
evsc

Reputation: 97

Using JavaScript Reduce to generate an object

I have an array of numbers [22, 44, 12, 9, 4, 23, 1, 11, 10, 5, 2, 123], I need to use reduce to create an object that looks like this:

{  
   numbersLessThanTen: [...],  
   numbersGreaterThanTen: [...]
}

I have the solution, which is the below:

const listOfNumbers = [22, 44, 12, 9, 4, 23, 1, 11, 10, 5, 2, 123];


const groupedBySize = listOfNumbers.reduce((total, next) => {
  const less = total.numbersLessThanTen || [];
  const more = total.numbersGreaterThanTen || [];
  
  next > 10 ? total.numbersGreaterThanTen = [].concat(more, next) : total.numbersLessThanTen = [].concat(less, next);
  return total;
  
}, {});

My question is, why does the following not work? It just returns the initial value. It works when I use .push() instead of .concat() but I really want to understand why this way does not work. Thank you!

const groupedBySize = listOfNumbers.reduce((total, next) => {
  // const less = total.numbersLessThanTen || [];
  // const more = total.numbersGreaterThanTen || [];
  
  next > 10 ? total.numbersGreaterThanTen.concat(next) : total.numbersLessThanTen.concat(next);
  return total;

}, {numbersGreaterThanTen: [], numbersLessThanTen:[]});

Upvotes: 6

Views: 1345

Answers (4)

Akrion
Akrion

Reputation: 18515

Array.concat does not mutate the array on which it is called. You are assuming that and not assigning the result to anything ... as per docs:

This method does not change the existing arrays, but instead returns a new array

So to get what you want with your code you could do something like this:

const listOfNumbers = [22, 44, 12, 9, 4, 23, 1, 11, 10, 5, 2, 123];

const groupedBySize = listOfNumbers.reduce((total, next) => {  
  let key = next > 10 ? 'numbersGreaterThanTen' : 'numbersLessThanTen'
  total[key] = total[key].concat([next]); // <-- assign the result
  return total;
}, {numbersGreaterThanTen: [], numbersLessThanTen: []});

console.log(groupedBySize)

Alternatively you could just do this:

const listOfNumbers = [22, 44, 12, 9, 4, 23, 1, 11, 10, 5, 2, 123];

const groupedBySize = listOfNumbers.reduce((ttl, nxt) => 
 (ttl[nxt > 10 ? 'numbersGreaterThanTen' : 'numbersLessThanTen'].push(nxt), ttl), 
 {numbersGreaterThanTen: [], numbersLessThanTen:[]});

console.log(groupedBySize)

Which does the same thing but uses Array.push which mutates the array on which it is called by inserting the item.

Upvotes: 0

Mark
Mark

Reputation: 92440

You can decide which array with the ternary and then push() instead of concat():

let n = [22, 44, 12, 9, 4, 23, 1, 11, 10, 5, 2, 123]

let obj = n.reduce((obj, n) => {
    (n < 10 
     ? obj.numbersLessThanTen
     : obj.numbersGreaterThanTen
    ).push(n)
    return obj
}, {numbersLessThanTen: [],  numbersGreaterThanTen:[]})

console.log(obj)

Upvotes: 1

Scott Sauyet
Scott Sauyet

Reputation: 50787

Others explained why that approach didn't work, and gave reasonable answers, continuing the mutation.

Here is an approach that doesn't mutate any data:

const listOfNumbers = [22, 44, 12, 9, 4, 23, 1, 11, 10, 5, 2, 123];

const groupedBySize = nbr => nbr.reduce 
  ( ({ gt10, lte10 }, next) => next > 10 
     ? { gt10: gt10 .concat(next), lte10 } 
     : { gt10, lte10: lte10 .concat(next) }
  , { gt10: [], lte10: [] }
  )

console.log(groupedBySize(listOfNumbers))

I also shorted the property names. Obviously you could re-lengthen them. But do note that "numbersLessThanTen" is a bit misleading, as it includes 10.

Upvotes: 1

Dacre Denny
Dacre Denny

Reputation: 30360

The reason your code doesn't work is becase concat() returns the result of concatentation as a new array. When your reduce function runs, the concatentation work is done, but the result of that concatentation isn't assigned to anything (ie a field in total). This is the reason the output result appears is identical to the input - the arrays in total never actually get updated. The following is from MDN

The concat method creates a new array consisting of the elements in the object on which it is called.

The push() method on the other hand actually mutates/updates the array that it's called on, adding what ever data you pass directly to that array instance. This means when push() is called per iteration, the arrays in total are updated directly which is why this approach works:

The push method appends values to an array.

Upvotes: 1

Related Questions