flen
flen

Reputation: 2386

Is it possible to access nth depth element of an array by specifying an array of indexes?

Update

Thank you all for your inputs, thanks to you I realize now I've made a seriously wrong assumption about JS. Actually, the code provided below works fine, if one modifies it:

...

  let currentPlaceInArray;
  for (let i = 0; i < position.length; i++) {
    let place = position[i];
    if (i + 1 === position.length) return currentPlaceInArray[place] = content; // Added this line now
    /***Explanation:
    Without this added line, the currentPlaceInArray variable is reassigned in the end
     to 'needle' in the present example. Thus, assigning it to something else (like
    currentPlaceInArray = ['updated']) will not have any effect on the array, because
    currentPlaceInArray is not assigned to it anymore, it's assigned to the string 'needle'
    ***/
    currentPlaceInArray = currentPlaceInArray ? currentPlaceInArray[place] : myArray[place]; 
  }


Original problem

I have a large unstructured multidimensional array, I do not know how many nested arrays it has and they have different lengths, like in this simplified example:

var myArray =
[
  [
    [],[]
  ],
  [
    [],[],[],[]
  ],
  [
    [],
    [
      [
        ['needle']
      ]
    ]
  ]
];

I want to be able to update 'needle' to something else. This would be possible by executing myArray[2][1][0][0] = 'updated';

I want to create a function which takes as parameters just 2 pieces of information: 1) the string to be written and 2) an array with the position of the array item to be updated. In the above case of myArray, it would be called thus: changeNeedle('updated', [2, 1, 0, 0]).

However, I can't assign a variable to an array key, just to its value. If it was possible to assign a variable to the array key, I could just update that variable with the current position (i.e., var currentPosition would be myArray[x], and currentPosition[y] would then be myArray[x][y]) [Update: it's the exact opposite, assigning a variable to an array will point exactly to that array, so currentPosition[y] === myArray[x][y]]. Like so:

function changeNeedle(content, position) {
  /***
  content: a string
  position: an array, with the position of the item in myArray to be updated
  ***/

  let currentPlaceInArray;
  for (let i = 0; i < position.length; i++) {
    let place = position[i];
    currentPlaceInArray = currentPlaceInArray ? currentPlaceInArray[place] : myArray[place]; 
  }
  currentPlaceInArray = content;
}

Is it possible to implement this changeNeedle function without using eval, window.Function() or adding a prototype to an array?

Upvotes: 0

Views: 573

Answers (4)

gyohza
gyohza

Reputation: 836

At first it looked like Chase's answer was kind of right (using recursion), but it seems you want your code to follow only the path provided to the function.

If you are certain of the position of the element you want to change, you can still use a recursive approach:

Pure JS recursive solution

var myArray = [
  [
    [],
    []
  ],
  [
    [],
    [],
    [],
    []
  ],
  [
    [],
    [
      [
        ['needle']
      ]
    ]
  ]
];

function changeNeedle(arr, content, position) {

  // removes first element of position and assign to curPos
  let curPos = position.shift();

  // optional: throw error if position has nothing
  // it will throw an undefined error anyway, so it might be a good idea
  if (!arr[curPos])
    throw new Error("Nothing in specified position");

  if (!position.length) {
    // finished iterating through positions, so populate it
    arr[curPos] = content;
  } else {
    // passes the new level array and remaining position steps
    changeNeedle(arr[curPos], content, position);
  }

}

// alters the specified position
changeNeedle(myArray, 'I just got changed', [2, 1, 0, 0]);

console.log(myArray);

However, if you want to search for the element that corresponds to the content, you still have to iterate through every element.

Upvotes: 1

Chase
Chase

Reputation: 3126

If you'd like to avoid importing a library, a array-specific solution with in-place updates would look something like:

function changeNeedle(searchValue, replaceWith, inputArray=myArray) {
  inputArray.forEach((v, idx) => {
    if (v === searchValue) {
      inputArray[idx] = replaceWith;
    } else if (Array.isArray(v)) {
      changeNeedle(searchValue, replaceWith, v);
    }
  });
  return inputArray;
}

Then you can call changeNeedle('needle', 'pickle', myArray); and your myArray value will be mutated to change instances of needle to pickle.

Return value and myArray would then look like:

JSON.stringify(myArray, null, 4);
[
    [
        [],
        []
    ],
    [
        [],
        [],
        [],
        []
    ],
    [
        [],
        [
            [
                [
                    "pickle"
                ]
            ]
        ]
    ]
]

Update: Based on your reply, a non-recursive solution that follows the exact path of a known update.

function changeNeedle(newValue, atPositions = [0], mutateArray = myArray) {
  let target = mutateArray;
  atPositions.forEach((targetPos, idx) => {
    if (idx >= atPositions.length - 1) {
      target[targetPos] = newValue;
    } else {
        target = target[targetPos];
    }
  });
  return mutateArray;
}

See: https://jsfiddle.net/y0365dng/

Upvotes: 1

hopeless-programmer
hopeless-programmer

Reputation: 990

No, You can't.

The simplest way to do this is to store the array which you are going to modify (array = myArray[2][1][0]) and the key (index = 0). Then you just need to make a regular assignment like array[index] = value.

You can consider the following implementation:

function changeNeedle(value, keys) {
  let target = array;

  for (const i of keys.slice(0, -1)) {
    target = target[i];
  }

  target[keys[keys.length - 1]] = value;
}

Upvotes: 0

Mario Vernari
Mario Vernari

Reputation: 7294

I suggest to consider the well-tested Lodash function update (or set, and so on). I think that's very close to what you need.

Upvotes: 1

Related Questions