FAQ: Iterators - Review

The first now at least doesn’t copy the existing array but still copies the new one from the subarray and trying to figure out how that turns out…uhh I’d reach for another solution.

That terrifies me. It could be implemented as an O(1) operation, but it might also be O(N), as much work as copying the whole array each time.
Also, don’t need it, only need to read, not modify, probably don’t even want to modify the original. So changing this to instead copy the elements from the source to the result array would make this version do the “right” thing. (and copying here is fine because it is done once, directly from the original location to the result with no intermediaries

1 Like

Thank you again! As you say, we don’t have to modify remaining.

const addWhileFlattening = (addToThis, remaining) => {
  // while (remaining.length > 0) {
  // let addThis = remaining.shift();
  remaining.forEach(addThis => {
    if (Array.isArray(addThis)) {
      addWhileFlattening(addToThis, addThis);
    } else {
      addToThis.push(addThis);
    }
  });
}

I’d be tempted to write that as a generator so that there’s no result array, instead yielding values as they are visited:

function* flatten(arr) {
  for (let elem of arr) {
    if (Array.isArray(elem)) {
      yield* flatten(elem)
    } else {
      yield elem
    }
  }
}

const multiLayeredArray = [1, 2, 3, [4, 5, 6, [7, 8, [3]]], [9, 10]]
console.log(Array.from(flatten(multiLayeredArray)))

Buuut yield* might very well do iteration at each level instead of fully deferring to the inner-most yield so … meh. Again, better to go with something where it can easily be argued that it’s doing the right thing.

Another option is to have an enclosing function that has a variable for the array, and then have a recursive function within it so that the result array doesn’t need to be passed around (which feels silly when we’re relying on side-effects anyway)

function flatten(arr) {
  const rec = arr => {
    for (let elem of arr) {
      if (Array.isArray(elem)) {
        rec(elem)
      } else {
        result.push(elem)
      }
    }
  }
  const result = []
  rec(arr)
  return result
}

…that’s a bit longer. but it appeals to me to separate the traversing and the collecting. And, this could be generalized a bit further by creating something that behaves much like Array.prototype.reduce, but one that acts on nested array instead. So this function would do the traversing, and you could send in a function that says how to collect. You could for example send in (a, b) => a + b to get the sum of the whole thing

Another thought is that if I had a tree structure like this, then I’d prefer a type dedicated to it instead of using arrays, and then keep only things of similar shape in an array. You certainly can though, it’s not evil. But the more common and reasonable case is to only flatten one level.
What’s weird about it is that you usually keep similar things in an array, that’s … kind of the reason you’d group them together. But this has several different shaped things in the same array.
Can you think of a situation when you would need a nested array with varying amounts of nesting?

1 Like
function fold(combine, acc, nestedArr) {
  const rec = arr => {
    for (let elem of arr) {
      if (Array.isArray(elem)) {
        rec(elem)
      } else {
        acc = combine(acc, elem)
      }
    }
  }
  rec(nestedArr)
  return acc
}

const multiLayeredArray = [1, 2, 3, [4, 5, 6, [7, 8, [3]]], [9, 10]]
const add = (a, b) => a + b
const mul = (a, b) => a * b
const collect = (arr, x) => (arr.push(x), arr)

console.log(fold(add, 0, multiLayeredArray))
// ^ 58
console.log(fold(mul, 1, multiLayeredArray))
// ^ 10886400
console.log(fold(collect, [], multiLayeredArray))
// ^ [ 1, 2, 3, 4, 5, 6, 7, 8, 3, 9, 10 ]
1 Like

From your replies I now learned about the Generator object and the yield keyword for the first time and a meaningful example how to generalize functions.

I also agree with that it is not practical to assume arrays whose elements are not in a similar shape (except for exercises). Instead of using such an array, I will consider another data type.

If you feel like it you could implement map. (otherwise, don’t, … obviously)

It would visit all elements and apply the function, producing an identical structure but with the function applied to each value.

So…

const multiLayeredArray = [1, 2, 3, [4, 5, 6, [7, 8, [3]]], [9, 10]]
map(x => x + 1, multiLayeredArray)
// ^ [2, 3, 4, [5, 6, 7, [8, 9, [4]]], [10, 11]]

Replacing them on the original is probably a whole lot easier, but making a copy as it goes would be preferable. (I haven’t tried it with javascript, but I think I will once I get back from grocery shopping)

My initial thought is that it’s probably trivial to copy as you’d do it recursively and the base case would be a single value …

1 Like

Is it like this?

const map = (funct, nestedArr) => {
  return nestedArr.map(elem => {
    if (Array.isArray(elem)) {
      return map(funct, elem);
    } else {
      return funct(elem);
    }
  });
}

Sometimes writing a recursive function leaves you with bit of a “wait what, was that it” …

So then, if this … thing can be mapped over, then what else?

How about functions?

const map = ...
const digits = ...
const sum = ...
const digitSum = map(sum, digits)
digitSum(123)
// ^ 6

if map creates an identical structure, and the structure is a function, then mapping over a function should create another function, but the thing inside (the result calling the function) will have had a function applied to it

mapping sum over digits, then, would create a function that obtains digits, but before the result falls out, sum gets applied, so the result of the new computed function is the sum of digits

I’m trying to experiment with iterator methods in JS, but I’m not yet literate in documentation.

I see there are some “optional” arguments for many certain iterator methods, including the ones covered in this module. I don’t understand the proper syntax to use them, however, as the documentation doesn’t read intuitively to me yet.

For example, the .map() iterator allows me to pass in a callback function as well as an index–but I don’t know where to put this index in the code, and it’s not clear to me how adding one to the code affects the functionality of the method. How do I make sense of the documentation? And what does this optional argument do?

1 Like

no it doesn’t, it provides an index to your callback

I’m not entirely sure I understand. Do you mean to say that the method is implicitly iterating through each index of the array on which is it applied and treating the element at each index as a new argument for the callback function?

That makes sense–it’s more or less the definition of an iterator as I understand it–but I don’t understand why it’s there in the documentation, like so:

Array.prototype.map(callbackFn(currentValue[, index[, array]])[, thisArg])

Pay more attention to the braces and commas

Array.prototype.map(
  callbackFn(currentValue[, index[, array]])
  [, thisArg]
)

and indeed, read documentation
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Here are my answers to the challenges of the iterator lesson:

Feedback is welcome.

I don’t know if I fully understood the instructions. But I think it’s coming up with a multi-layered array, and merging it all into one…I think. Here’s my solution:

const sumNum = [[1,2,3],[4,5,6]].reduce((accumulator, currentValue) => {
     return accumulator.concat(currentValue)
});

console.log(sumNum);

answer: [ 1, 2, 3, 4, 5, 6 ]

What do you think?

1 Like

Hi there,

Sorry I know you are saying pay attention but I am unable to work out how to include the index when using an array method to answer the instruction

"Use the optional arguments in an iterator to include the index or the entire array. "

I have read the MDN documentation for .map() and tried to implement the syntax every way I could thing of to include the index argument. I would be grateful if someone could just show how you include the index argument on an extremely basic level.

Array1.map(functionName)

//Say I have declared the function elsewhere obviously. how do I provide an index as an argument? what does this actually do?

Sorry if I am getting the wrong end of the stick here.

If you’re not sure what arguments are passed to your function, then you could print them out

And no, you do not provide an index. You are provided an index.

…okay

I understand that I am being taught to ‘learn to troubleshoot’ but help a brother out here

ah right okay

const functionName= (number,index,array)=> {
console.log(number)
console.log(index)
console.log(array)}
const Array1=[1,2,4,5,6,7,8,9]

const mm=Array1.map(functionName)
console.log(mm)

Array.map() does not need need more than one parameter in the callback… the value. The iterator sends one value at a time to the callback. What do we want to map from our original array?

const square = x => x ** 2

squares = [1, 2, 3, 4, 5, 6, 7, 8, 9].map(square)

console.log(squares)  // [1, 4, 9, 16, 25, 36, 49, 64, 81]

Hi there! Anybody willing to provide a practical example (just for clarity and understanding purposes) of the .reduce( ) method on arrays, and extend it to include a greater understanding of the task listed in the ‘challenge yourself’ section - so that it takes a multi-layered array and returns a single layer array from scratch? Would be so helpful for my understanding of what .reduce( ) is used for, and how to make it more robust! Thank you!