0

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?

4 Answers 4

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Oh, wow, it was right in front of my face all of this time! I didn't know JS accepted myArray[1,2,3,4] as valid syntax! Thank you so much! I thought I'd have to use myArray[1][2][3][4]
No, it doesn't, I think! I think it's not throwing an error... but it's not really working the way you want. If you try accessing myArray[2, 1, 0, 0] it will only access myArray[0]. That's because it's interpreting the commas as comma operators, so it returns only the last statement. (I'm really not using it this way in my solution, in case you got that impression from reading it. I really am iterating through the elements of the position array!)
Of course!X) I should be sleeping... Now I got why your recursion works, obviously because you're passing the nested array as the argument for the recursion
Can I ask you to rephrase the post title? Maybe something along the lines of "Is it possible to access nth depth element of an array by specifying an array of indexes?". I only say that because the impression I got from your title is that you were looking for Symbol wrappers, which do allow us to use objects or arrays as keys of an object. Pretty interesting concept, also, but not applicable as a solution to your particular problem, I think.
1

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.

1 Comment

Thanks a lot! It does look just like what I want. I'll try to find out how Lodash implements in vanilla JS it because I didn't want to load a library just for this
1

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/

3 Comments

Thank you! But I think this solution is looping through all items of the array. I wanted to avoid this and go directly to the ['needle'] array by calling as argument the position [2,1,0,0] (instead of searching through all arrays (thousands of them))
Ah, got it. Understood, and you are correct. I added a direct manipulation viersion that doesn't require recursion if your other solution does not end up working out.
Thank you so much for your answer! You're right, this solves it perfectly. You made me realize that I made a mistake previously. My above changeNeedle function only didn't work because in the end the currentPlaceInArray variable is reassigned to a string ('needle'). It's very late and it's been a while since I coded in JS, I assumed it wasn't working because the variable assignment was cloning the list (creating a new list and pointing to it in memory instead) instead of pointing to the original list. Except it was, until I reassigned the variable to something else. Sorry for this
0

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;
}

3 Comments

Thanks! But how can I store the array I want to modify? This is my original question. Continuing with my example: var store = myArray[0]; store = [12]; console.log(myArray[0]) //outputs [[], []]
Updated the answer.
Thanks for the update, but I still don't follow exactly how it's supposed to work. Could you maybe us a concrete example? (though I should note that this question has been answered already)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.