I Can't push to the accumulator in this function

Hi Folks,

I came up with this solution to the squareList challenge:

const squareList = (arr) => {

  let a = [];// had use this as a workaround

  arr.reduce((acc, curr) => {
    if(Number.isInteger(curr) && curr > 0) {  
      a.push(curr ** 2);// can't push to acc? 
    }
  },[]) // acc is initialised as an empty array
  return a; // return a instead of acc :(
}

const squaredIntegers = squareList([3, 4.8, 5, 3, -3.2]);
console.log(squaredIntegers);

I’m not entirely happy because I seem unable to push the current value to the accumulator, which has been initialised as an empty array. This kinda points to a mis-understanding, or a syntactical error of some sorts.

Taking a look at the second solution to the challenge, concat has been used to append to the accumulator:

sqrIntegers.concat(num * num)

which works fine in the provided solution. However, any reference I make to acc (the accumulator) throws an error.
e.g.

acc.push(curr ** 2);

or

acc.concat(curr ** 2);

throws:

TypeError: acc is undefined

Doh! How annoying! Would someone kindly explain what this is happening and how to resolve it please.

  1. The callback runs once per value in the array.
  2. First time it runs, the accumulator is the value you give it as the second argument ment to reduce.
  3. Next time the callback runs, the value you return from the callback is now the accumulator.
  4. You don’t return anything, so the accumulator is now the value undefined.
  5. Can’t push to a undefined, error
1 Like

A couple more things:

Rather than use an if statement in your reducer function, use arr.filter to pass through only integers.

After filtering, you don’t need reduce anymore – you can now use map. In general, any time you append to a list in a loop, you can usually substitute map instead. Although you can do anything at all with reduce, it’s best to always use the simplest HOF (Higher Order Function) you can.

Also, it’s not good practice to mutate anything inside of a HOF as you’re doing with a, though in this case it’s harmless. The idea is that the reducer function should be able to stand alone without any state needed in the outer scope. In this case, you can have your reducer function return [...acc, curr ** 2] – but again, you can just use map instead.

2 Likes

@DanCouper & @chuckadams,

Thank you so much guys for taking the time to explain this with clarity.

…itsABreathOfFreshAir

I’ve been developing a complex about return statements just recently, thrown into episodes of complete discombobulation.

I’m sure this is normal for someone who has just donned their waders before entering the deeper end of the pool.

I’ll add some more to this feed after I’ve regurgitated the gems within.

Thanks again guys,

LT

1 Like

@DanCouper, @chuckadams
Hey guys,

Thanks again for giving me such excellent input. It spurred me on to re-write my function without trying to push to an undefined! Or, using a variable outside of the scope of the reducer function :slightly_smiling_face:

There’s a comment / question or two that I’d like to add in response to your replies if you wouldn’t mind.

Just for clarification:

First time it runs, the accumulator is the value you give it as the second argument ment to reduce.

This means that the first time the function runs:
arr.reduce((acc, curr) => {}

acc = curr
acc is the same value as curr (this is what you meant ?)

…Quote

Rather than use an if statement in your reducer function, use arr.filter to pass through only integers.

This worked really well. I replaced the if statement with filter() and then used map() to return the correct values.

Quote…

it’s not good practice to mutate anything inside of a HOF as you’re doing with a

…that was an ugly thing to do :slight_smile: and is what caused me post my original concerns.

the reducer function should be able to stand alone without any state needed in the outer scope

Yup, I managed to achieve that using reduce() filter() map() thanks to your advice.

I didn’t manage to implement this though:

In this case, you can have your reducer function return [...acc, curr ** 2]

Did you mean this to be written into the if statement that I initially used? I got an error along the lines of:
attempt to iterate through an un-iterable object
when I tried to use it, or something of the sorts. I’d really like to know how I could have written it into my code. When I first saw it, I thought I could just plug it into the end if the code that I had.

This, however, is the solution I came up with in the end:

const squareList = (arr) => {
  const result = 
  arr.reduce((acc, curr) => {
    return arr.filter(curr => Number.isInteger(curr) && curr > 0)
    .map(curr => curr ** 2)
  },[])
  return result;
};

const squaredIntegers = squareList([-3, 4.8, 5, 3, -3.2]);
console.log(squaredIntegers);

I think this much better than what I originally had. Thanks for your help.

No. So reduce takes two arguments, a callback and an initial value. The accumulator starts as the first value, then the callback merges that with the current value, returning a new accumulator, which goes into the next call to the callback and so on.

[1,2,3].reduce((acc, curr) => acc + curr, 10)
// acc starts at 10, so it goes 10 + 1 + 2 + 3
// returns 16

If you miss off the second argument, then the initial value for the accumulator is the first element in the array, and the fist value for current is the second value:

[1,2,3].reduce((acc, curr) => acc + curr)
// acc starts at 1, so it goes 1 + 2 + 3
// returns 6 (curr starts at second value)

Sort of, but the issue is is that you’re now mixing up the sequence of how things work a bit.
So you can just use reduce here:

  1. use an if to check if the current value is a positive integer.
  2. If it is, add that value to the accumulator array.
  3. If it isn’t, don’t add it.
  4. Then return the accumulator.

This was the main issue in your original code: the callback for reduce that has to take two values (an accumulator whatever the current value in the array is), and has to return a value that is the same type as the accumulator. So if you have an array of numbers, and you want to put the end result in an array, the callback function has to take an array and a number, and return an array (which will be the result of merging those two arguments).

So this is a simpler version of a solution, using a loop instead of reduce (I’ve ignored checking for integers, just squares every number in an array). Hopefully it’s slightly easier to see what’s happening when written imperatively?

// Given an array and a number, returns
// a new array same as the input but with
// the number added onto the end
function concatSquaredNum (acc, n) {
    return acc.concat(n ** 2);
  }

function doTheThing(numbers) {
  // Set up initial value
  let acc = [];
  for (const n of numbers) {
    acc = concatSquaredNum(acc, n);
  }
  return acc;
}

This are all doing the same thing:

doTheThing([1, 2, 3])
// [1, 4, 9]
[1, 2, 3].reduce((acc, n) => concatSquaredNum(acc, n), [])
// [1, 4, 9]
[1, 2, 3].reduce(concatSquaredNum, [])
// [1, 4, 9]
[1, 2, 3].reduce((acc, n) => {
  return acc.concat(n ** 2);
}, [])
// [1, 4, 9]
[1, 2, 3].reduce((acc, n) => [...acc, n ** 2], [])
//  [1, 4, 9]

If you’re using map/filter, you don’t need reduce. reduce takes an array of values and converts to another type of value. That type can also be an array, or it can be anything else. So for example: array of numbers to a single number, array of strings to an object, array of mixed numbers to an array of numbers that are all positive integers, etc.

Map and filter are like specialised versions of reduce that do one specific thing.

Map takes an array of values of one type and maps to an array of values of a different type on a one-to-one basis: you run the callback function on each value in turn, and it returns a transformed value, and the new array is made up of those transformed values. So for example you have an array of numbers and you want an array of this numbers squared. These do the same thing, just map is a lot clearer:

[1,2,3].reduce((acc, n) => acc.concat(n ** 2), [])
[1,2,3].map(n => n ** 2)

Filter takes an array of values of one type and returns an array of values of the same type (that is the same size or smaller than the input). It tests each value in turn, if it returns true it adds the value to the output array, if false it skips that value. These do the same thing, just filter is a lot clearer:

[1,2,3].reduce((acc, n) => {
  if (Number.isInteger(n) && n > 0) {
    return acc.concat(n);
  } else {
    return acc;
  }
}, [])
[1,2,3].filter(n => NumberisInteger(n ) && n > 0)

So if you filter then map, you don’t need reduce, and if you want to put all the logic inside reduce, you don’t need filter or map. Putting all the logic inside reduce will be more efficient, but the trade-off will be that it’ll also be harder to read and to write :man_shrugging:t3:

1 Like

No. So reduce takes two arguments, a callback and an initial value. The accumulator starts as the first value, then the callback merges that with the current value, returning a new accumulator, which goes into the next call to the callback and so on.

[1,2,3].reduce((acc, curr) => acc + curr, 10)
// acc starts at 10, so it goes 10 + 1 + 2 + 3
// returns 16

If you miss off the second argument, then the initial value for the accumulator is the first element in the array, and the fist value for current is the second value:

[1,2,3].reduce((acc, curr) => acc + curr)
// acc starts at 1, so it goes 1 + 2 + 3
// returns 6 (curr starts at second value)

Thank you for taking the time to explain this concisely. It is crystal clear now :eye: :eye:

Yes it certainly is, thanks.

I really appreciate the lengths you’ve gone to explain this, I cetainly hadn’t expected it, but it cetainly going into my reference pile :slight_smile:

Do you know how long I stared at my solution asking myself “why am I using that reduce() when I can see that filter() and map() are doing what is needed?”

The only reason I could come up with was that it had been suggested that I could replace the if statement within my reducer function with filter .

I’m glad you put me straight on this. Thank you.

I love the way you have explained this:

map() does look sweeter and as long as you have it in your brain what map() is doing here: takes n in, squares n, puts n in array. And it is this last thing ‘puts n in array’ that is quietly invisible in the function, that can cause the brain to go into meltdown. Whereas the reduce() method clearly states ‘I am concatenating n * n onto acc’

My brain see’s the reduce() method far clearly at first glance and then has to work out what is happening with map(). But that’s just me I think :crazy_face:

I think what you have written is fantastic and I greatly appreciate the effort you have put in to do so. As I said, this post is definately being filed as my resource on reduce() filter() and map()
Cheers,
LT