4
\$\begingroup\$

I primarily work with C#, and when working with an array of arrays, I can sum the length of the sub arrays using Linq:

var myArray = new int[2][] {
    new int[3] { 1, 2, 3 },
    new int[4] { 1, 2, 3, 4 }
};
myArray.Sum(s => s.Length);

Printing the result of that sum to a console should produce a 7. With that in mind, I'm having trouble finding resources on a simple way to do this in JavaScript, and as such I'm stuck doing it with nested for iterators:

let containedLength = 0;
let myArray = [ [1, 2, 3], [1, 2, 3, 4] ];
for (let x = 0; x < myArray.length; x++)
    for (let y = 0; y < myArray[x].length; y++)
        containedLength++;

I could write a function to do this, but that would be over the top for what I'm trying to do today since I already have a basic way to do it. Is there a simpler way similar to C#'s Linq that could get the sum of the lengths of arrays, within an array of arrays?

\$\endgroup\$
2
  • 4
    \$\begingroup\$ For the sake of completeness: the analogous javascript would be myArray.map(a => a.length).reduce((a, b) => a + b, 0) \$\endgroup\$
    – Quelklef
    Oct 1 '21 at 4:33
  • 7
    \$\begingroup\$ @Quelklef: …or just myArray.reduce((a, s) => a + s.length, 0) \$\endgroup\$ Oct 1 '21 at 14:37
13
\$\begingroup\$

There's no need for the inner loop to compute the length element by element. It's better to use the .length property on each row, reducing the inner loop time complexity to O(1), and covert your C-style loop to a for..of loop which is less prone to errors:

const arr = [[1, 2, 3], [1, 2, 3, 4]];
let innerLength = 0;

for (const row of arr) {
  innerLength += row.length;
}

console.log(innerLength);

(You can see a few style preferences here; use vertical whitespace and braces around for/if blocks, prefer const to let, avoid "my" in var name)


The above code isn't satisfying relative to the C# code, though. JS has a much less expressive API for array opterations than Linq. The normal approximation is to use map/reduce/flat/flatMap/filter-type functions. For example, if flat is available:

const arr = [[1, 2, 3], [1, 2, 3, 4]];
console.log(arr.flat().length); // flatten 1 level only, like original code

But the problem is that this is O(n) space, unlike your O(1) space code. We allocate and build a whole new array just to take its .length, then send it to the garbage collector.

Array#reduce can give you the functional style but preserving O(1) space and is probably the closest we can get to C# without helper functions or third-party libraries:

const arr = [[1, 2, 3], [1, 2, 3, 4]];
console.log(arr.reduce((a, e) => a + e.length, 0));

This is probably a little slower than for..of (due to function call overhead) and isn't quite as elegant as flat, but might be a good balance depending on your use case.

It's good not to prematurely optimize though, so flat() is fine for starters.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ I like your answer in general, but I do slightly disagree with the last paragraph. It's indeed good not to spend significant effort on prematurely optimizing something that hasn't been determined to be an issue, especially if such optimization might introduce bugs or make the code harder to read. But it's also good not to deliberately litter your code with known inefficient idioms, when a clean and more efficient alternative exists. I would consider array.flat().length to be an example of such a pointlessly wasteful idiom that should be avoided merely on principle. \$\endgroup\$ Oct 1 '21 at 14:43
  • 2
    \$\begingroup\$ My point is that although JS array funcs tend to be space inefficient, it's fine to use them anyway and focus on bigger problems. In this particular case, the reduce option happens to be O(1) space and also quite clean, but usually you don't get so lucky and it's just better to use flat, map or filter and trade space for readability. reduce can get ugly quickly. It's a use-case-specific judgment call, and I'd err on the side of whatever's easiest first. "Fine for starters" seems like an appropriately mild endorsement. \$\endgroup\$
    – ggorlen
    Oct 1 '21 at 15:59
  • 1
    \$\begingroup\$ I would like to point out that reduce is a common name (the even more common name being fold) for what .NET LINQ calls Aggregate, so it does exist in LINQ as well. Also, I would like to point out that reduce aka fold aka Aggregate is known to be a universal iteration operation, i.e. it can do everything a foreach can do, and thus it is not surprising that it can solve this problem, because it can solve all problems (that involve iterating over a collection). The Sum method in the question is essentially a restricted version of Aggregate. (Which is again trivially true … \$\endgroup\$ Oct 1 '21 at 19:46
  • \$\begingroup\$ because all collection operations are in some sense special cases of Aggregate, but here the equivalence is much closer. Basically, const sum = (arr, f) => arr.reduce((acc, ...args) => acc + f(...args), 0);.) \$\endgroup\$ Oct 1 '21 at 19:52
2
\$\begingroup\$

the simple way is to flatten out the array then get the length as it flattens out all the array.

let myArray = [ [1, 2, 3], [1, 2, 3, 4] ];
console.log(myArray.flat().length);

That will output 7.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.