'Replace element at specific position in an array without mutating it
How can the following operation be done without mutating the array:
let array = ['item1'];
console.log(array); // ['item1']
array[2] = 'item2'; // array is mutated
console.log(array); // ['item1', undefined, 'item2']
In the above code, array
variable is mutated. How can I perform the same operation without mutating the array?
Solution 1:[1]
You can use Object.assign
:
Object.assign([], array, {2: newItem});
Solution 2:[2]
The fast way
function replaceAt(array, index, value) {
const ret = array.slice(0);
ret[index] = value;
return ret;
}
See the JSPerf (thanks to @Bless)
Related posts:
Solution 3:[3]
You can simply set up a a new array as such:
const newItemArray = array.slice();
And then set value for the index which you wish to have a value for.
newItemArray[position] = newItem
and return that. The values under the indexes in-between will have undefined
.
Or the obviously alternative would be:
Object.assign([], array, {<position_here>: newItem});
Solution 4:[4]
Well, technically this wouldn't be replacing as there isn't an item at the index you're changing.
Look at how it's handled in Clojure—a language that's built around canonical implementations for immutable data structures.
(assoc [1] 2 3)
;; IndexOutOfBoundsException
Not only does it fail, but it crashes too. These data structures are designed to be as robust as possible and when you come up against these kinds of errors, it's generally not because you've discovered an edge case, but more likely that you're using the wrong data structure.
If you are ending up with sparse arrays, then consider modelling them with objects or maps instead.
let items = { 0: 1 };
{ ...items, 2: 3 };
// => { 0: 1, 2: 3 }
let items = new Map([ [0, 1] ]);
items(2, 3);
// => Map {0 => 1, 2 => 3}
However, Map is a fundamentally mutable data structure, so you'd need to swap this out for an immutable variant with a library like Immutable.js or Mori.
let items = Immutable.Map([ [0, 2] ]);
items.set(2, 3);
// => Immutable.Map {0 => 1, 2 => 3}
let items = mori.hashMap();
mori.assoc(items, 2, 3);
// => mori.hashMap {0 => 1, 2 => 3}
Of course, there might be a perfectly good reason for wanting to use JavaScript's arrays, so here's a solution for good measure.
function set(arr, index, val) {
if(index < arr.length) {
return [
...arr.slice(0, position),
val,
...arr.slice(position + 1)
];
} else {
return [
...arr,
...Array(index - arr.length),
val
];
}
}
Solution 5:[5]
Here is how I'd like to do it:
function update(array, newItem, atIndex) {
return array.map((item, index) => index === atIndex ? newItem : item);
}
Generally, Array-spread operation produces few temporary arrays for you, but map
doesn't, so it can be faster. You can also look at this discussion as a reference
Solution 6:[6]
Another way could be to use spread operator with slice as
let newVal = 33, position = 3;
let arr = [1,2,3,4,5];
let newArr = [...arr.slice(0,position - 1), newVal, ...arr.slice(position)];
console.log(newArr); //logs [1, 2, 33, 4, 5]
console.log(arr); //logs [1, 2, 3, 4, 5]
Solution 7:[7]
How about this:
const newArray = [...array]; // make a copy of the original array
newArray[2] = 'item2'; // mutate the copy
I find the intent a bit more clear than this one-liner:
const newArray = Object.assign([...array], {2: 'item2'});
Solution 8:[8]
var list1 = ['a','b','c'];
var list2 = list1.slice();
list2.splice(2, 0, "beta", "gamma");
console.log(list1);
console.log(list2);
Is this what you want?
Solution 9:[9]
There's a new tc39 proposal, which adds a with
method to Array
that returns a copy of the array and doesn't modify the original:
Array.prototype.with(index, value) -> Array
Example from the proposal:
const correctionNeeded = [1, 1, 3];
correctionNeeded.with(1, 2); // => [1, 2, 3]
correctionNeeded; // => [1, 1, 3]
(note that a RangeError
will be thrown if the first argument to with
is outside the bounds of the array, so the specific example in the question will not work)
As it's currently in stage 3, it will likely be implemented in browser engines soon, but in the meantime a polyfill is available here or in core-js
.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Oriol |
Solution 2 | |
Solution 3 | |
Solution 4 | |
Solution 5 | just-boris |
Solution 6 | Saksham |
Solution 7 | Igor Sukharev |
Solution 8 | YSJ |
Solution 9 | Josh |