Function that would build an array of "needles" to access values from a nested array?

Viewed 44

I have a nested array that contains several strings at different levels.

let nested = [
  "ONE",
  [
    "TWO",
    "THREE",
    [
      "FOUR",
      "FIVE"
    ],
    "SIX",
    "SEVEN"
  ],
  [
    "HEIGHT"
  ],
  "NINE"
]

I have a working function that can traverse my nested array to retrieve a value, using an array of indexes as "needle":

const getValueByIndexes = (array,indexes) => {
  const children = array[indexes[0]];

  if(indexes.length > 1){
    return getValueByIndexes(children,indexes.slice(1));
  }else{
    return children;
  }
}

let test = getValueByIndexes(nested,[1,2,1]);

console.log(test); //this IS returning "FIVE", as expected

This works well; but my question here is that I need a function that would BUILD those needles; based on my nested array.

The output I want is:

[
  [0],//ONE
  [1,0],//TWO
  [1,1],//THREE
  [1,2,0],//FOUR
  [1,2,1],//FIVE
  [1,3],//SIX
  [1,4],//SEVEN
  [2,0],//HEIGHT
  [3],//NINE
]

How could I achieve that ? Thanks !

3 Answers

You could take a recursice approach by checking for arrays.

const
    getIndices = array => array.flatMap((v, i) => Array.isArray(v)
        ? getIndices(v).map(a => [i, ...a])
        : [[i]]
    ),
    data = ["ONE", ["TWO", "THREE", ["FOUR", "FIVE"], "SIX", "SEVEN"], ["HEIGHT"], "NINE"],
    result = getIndices(data); 

result.forEach(a => console.log(...a));
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can also use a recursive generator function:

function* build_arr(d, c = []){
    for (var i = 0; i < d.length; i++){
        yield* (!Array.isArray(d[i]) ? [[...c, i]] : build_arr(d[i], [...c, i]))
    }
}
let nested = ['ONE', ['TWO', 'THREE', ['FOUR', 'FIVE'], 'SIX', 'SEVEN'], ['HEIGHT'], 'NINE']
console.log(Array.from(build_arr(nested)))

The answers from Nina Scholz and Ajax1234 are both great. But it's also not hard to do this in a more generic way to handle arbitrary objects and their paths. Here is a function I use often for that:

const getLeafPaths = (obj) =>
  Object (obj) === obj
    ? Object .entries (obj) .flatMap (
        ([k, v]) => getLeafPaths (v) .map (p => [Array .isArray (obj) ? Number(k) : k, ...p])
      )
    : [[]]

let nested = ["ONE", ["TWO", "THREE", ["FOUR", "FIVE"], "SIX", "SEVEN"], ["HEIGHT"], "NINE"]

console .log (getLeafPaths (nested))
//=> [[0], [1, 0], [1, 1], [1, 2, 0], [1, 2, 1], [1, 3], [1, 4], [2, 0], [3]]
.as-console-wrapper {max-height: 100% !important; top: 0}

So it solves this problem, but it is also useful for other objects:

getLeafPaths ({foo: 1, bar: {baz: [{qux: 2}, {quz: 3}], corge: 4}, grault: [5, 6, 7]})

will yield:

[
  ["foo"], 
  ["bar", "baz", 0, "qux"], 
  ["bar", "baz", 1, "quz"], 
  ["bar", "corge"], 
  ["grault", 0], 
  ["grault", 1], 
  ["grault", 2]
]
Related