Proposal for Array.prototype methods for multi-dimensional arrays

@graphemecluster - here's just a few thoughts I have on some of your proposed methods. I think I would find a number of them to be incredibly useful, but there's also many others in which I'm skeptical about their overall utility.

buildShape() would be really nice to have. There's been countless times when I've needed to create an n X m array, and it's a little tedious to do using Array.from().

The .shapeAtOrigin() method honestly doesn't seem overly helpful. .shapeAtOrigin() is a little nice, because it provides a more declarative way to do a common task, but the type of code it's replacing isn't all that verbose or difficult to write.

// Instead of code like this:
function doThing(map) {
  for (let y = 0; y < map.length; ++y) {
    for (let x = 0; x < map[0].length; ++x) {
      ...
    }
    ...
  }
}

// We can write code like this:
function doThing(map) {
  const [height, width] = map.shapeAtOrigin()
  for (let y = 0; y < height; ++y) {
    for (let x = 0; x < width; ++x) {
      ...
    }
    ...
  }
}

So yes, using .shapeAtOrigin() improves the code a little bit, but the improvements are minimal, and it requires readers to know what the shapeAtOrigin() function does, while the first version is something that anyone can read and understand.

The problem with .shape() is that, 99% of the time, .shapeAtOrigin()-like behavior is all I need - I'm usually working with "multi-dimensional arrays" where each subarray is the same length, so I'm ok making this assumption and simply checking the length of the first element of each array. The difference between .shape() and .shapeAtOrigin() is only noticeable if I'm dealing with a nested array, whose subarrays may have different sizes. From what I can tell, .shape() will mostly just provide a convenient way to find the biggest length of a certain dimension, which will probably be occasionally useful, but I'm not I'm convinced that it's a common enough need to warrant a new method.

.nestedMap() seems like a really nice method to have, I've often needed this sort of thing. I wonder if it would be helpful, with some of these nested methods, to have the ability to specify a nesting depth. Usually, you're working with a fixed number of dimensions anyways, and adding the ability to specify a max-depth would allow these nested array to contain anything, including subarrays, without messing the logic up.

[['x', ['z', 'a'], 'yz'], ['a', 'bxy', 'c']]
  .nestedMap(x => x.length, { dimensions: 2 })
// That produces this:
[[1, 2, 2], [1, 3, 1]]

Note how .nestedMap() didn't traverse into ['z', 'a'] when mapping over the data, instead, because it knew it was only operating on two dimensions, it passed the entire ['z', 'a'] array as an argument.

.nestedForEach() looks like a helpful method as well. Even better would perhaps be to provide a function like .nestedEntries() that returns an iterator which yields a coordinate/value tuple or something, that way one can do nested iteration in a for-of loop.

.nestedSplit() and .nestedJoin() are interesting ideas, but it's not anything I'm too excited about. It feels to me that, if I ever have a scenario where I'm able to use a function like .nestedJoin(), it's more of a coincidence that I'm wanting to do a join on both the inner and outer dimensions.

The only time I've used .fill() is with the Array(length).fill(null) pattern, to create an array of a certain size (a pattern that can also be achieved via Array.from({ length }), () => null)). I've never used it on a pre-existing array, because I tend to avoid mutating my arrays. So, I personally wouldn't find .nestedFill() to be that useful, but maybe others would enjoy its use. Same thing goes for .nestedFillMap(), I'd rather receive a new array than mutate the current one.

The problem with .nestedIncludes(), .nestedFind(), .nestedFindLast(), .nestedSome(), and .nestedEvery() is that all of these behaviors can be achieved today by simply using .flat() first, e.g. yourArray.flat().includes(). It's true that there's a performance overhead for this sort of thing, but this could potentially be solved via the iterator-helpers proposal. It looks like that proposal doesn't currently provide an iterator.flat() function, but if we went and advocated for that, we would get the performance benefit of all these nested functions with a single new function (with .nestedFindLast() being the exception, that would need to be solved some other way).

As for .nestedIncludesFromLast(), .nestedSomeFromLast(), and .nestedEveryFromLast(), I'm not too gung-ho about introducing nested "fromLast" variants unless we have non-nested from-last versions of these functions as well.

Next, you have .nestedIndexOf(), .nestedLastIndexOf(), .nestedFindIndex(), and .nestedFindLastIndex(), which all seem pretty nice, and I would love to have functions like those available.

So, from your repository, the methods I'm seeing that I would care most about are as follows:

  • .buildShape()
  • .nestedMap()
  • .nestedForEach()
  • .nestedIndexOf()
  • .nestedLastIndexOf()
  • .nestedFindIndex()
  • .nestedFindLastIndex()

Perhaps others would be of a different opinion.

2 Likes