Reputation: 12524
I'm having a hard time figuring out how to move an element of an array. For example, given the following:
var array = [ 'a', 'b', 'c', 'd', 'e'];
How can I write a function to move the element 'd'
to the left of 'b'
?
Or 'a'
to the right of 'c'
?
After moving the elements, the indexes of the rest of the elements should be updated. The resulting array would be:
array = ['a', 'd', 'b', 'c', 'e']
This seems like it should be pretty simple, but I can't wrap my head around it.
Upvotes: 793
Views: 840058
Reputation: 9979
Eg: Find and move 'd' to 0th position:
let arr = [ 'a', 'b', 'c', 'd', 'e'];
arr = [...arr.filter(item => item === 'd'), ...arr.filter(item => item !== 'd')];
console.log(arr);
Upvotes: 4
Reputation: 7618
I like this method as it's concise and it just works.
function arraymove(arr, fromIndex, toIndex) {
var element = arr[fromIndex];
arr.splice(fromIndex, 1);
arr.splice(toIndex, 0, element);
}
Note: always remember to check your array bounds.
The following snippet prints a few tests in console to show all combinations of fromIndex
and toIndex
(0..n, 0..n) work.
Upvotes: 563
Reputation: 9344
One approach would be to use splice()
to remove the item from the array and then by using splice()
method again, insert the removed item into the target index.
const array = ['a', 'b', 'c', 'd', 'e']
const newArray = moveItem(array, 3, 1) // move element from index 3 to index 1
function moveItem(arr, fromIndex, toIndex){
let itemRemoved = arr.splice(fromIndex, 1) // assign the removed item as an array
arr.splice(toIndex, 0, itemRemoved[0]) // insert itemRemoved into the target index
return arr
}
console.log(newArray)
You can find a short explanation of splice() here
Upvotes: 8
Reputation: 1087
var array = [ 'a', 'b', 'c', 'd', 'e'];
const fromIndex = array.indexOf('e');
const toIndex = 1;
const element = array.splice(fromIndex, 1)[0];
array.splice(toIndex, 0, element);
console.log(array).
// output ['a','e', 'b', 'c', 'd']
Upvotes: -1
Reputation: 4832
Using modern JavaScript (as of 2023) without mutating the array:
function move(array, targetIndex, beforeIndex) {
if (targetIndex < beforeIndex) {
// Moves forward
return [
...array.slice(0, targetIndex),
...array.slice(targetIndex + 1, beforeIndex),
array.at(targetIndex),
...array.slice(beforeIndex),
];
}
if (targetIndex > beforeIndex) {
// Moves back
return [
...array.slice(0, beforeIndex),
array.at(targetIndex),
...array.slice(beforeIndex, targetIndex),
...array.slice(targetIndex + 1),
];
}
// Stayes in place
return [...array];
}
const letters = [ "a", "b", "c", "d", "e"];
// Move te d back, before the b
console.log(move(letters, letters.indexOf("d"), letters.indexOf("b")));
// Move the b forward, before the d
console.log(move(letters, letters.indexOf("b"), letters.indexOf("d")));
// Have the c stay in place
console.log(move(letters, letters.indexOf("c"), 3));
Upvotes: 0
Reputation: 98
You can use this to convert from Object to array, move the index than reconvert in Object
function x(obje, oldIndex, newIndex) {
var newArr = Object.entries(obje);
var finalObj = new Object();
newArr.splice(newIndex,0,newArr.splice(oldIndex,1)[0]);
//-- Convert again in object
finalObj = newArr.reduce( (obj, item) => Object.assign(obj, { [item[0]]: item[1] }), {});
return finalObj;
}
var obj = {
"x.pdf": {
"sizeFmt": " <samp>(107.64 KB)</samp>"
},
"y.pdf": {
"sizeFmt": " <samp>(364.34 KB)</samp>"
},
"z.pdf": {
"sizeFmt": " <samp>(111.81 KB)</samp>"
}
};
console.log(obj);
obj = x(obj, 0, 1);
console.log(obj);
Upvotes: 0
Reputation: 192
With Angular U could use the angular/cdk/drag-drop API. Either when actually using drag&drop or as standalone:
npm install @angular/cdk
app.module.ts:
import {DragDropModule} from '@angular/cdk/drag-drop';
In componentXY.ts:
import { CdkDragDrop, CdkDragEnd, CdkDragStart, moveItemInArray } from '@angular/cdk/drag-drop';
onDrop(event: CdkDragDrop<any>) {
// handle drop event
console.log("onDrop", event)
moveItemInArray(this.codes, event.previousIndex, event.currentIndex);
}
Upvotes: 0
Reputation: 340
@abr's answer is near perfect, but it misses one edge case where if you try and access a key that does not exist in the array, it actually returns an array 1 entry larger than the original. This fixes that bug.
let move = (arr, from, to) => {
const smallerIndex = Math.min(from, to);
const largerIndex = Math.max(from, to);
/**
Bail early if array does not have the target key, to avoid
edge case where trying to move an undefined key
results in the array growing 1 entry in size with an undefined value
*/
if(!arr.hasOwnProperty(from)) return arr
return [
...arr.slice(0, smallerIndex),
...(
from < to
? arr.slice(smallerIndex + 1, largerIndex + 1)
: []
),
arr[from],
...(
from > to
? arr.slice(smallerIndex, largerIndex)
: []
),
...arr.slice(largerIndex + 1),
];
}
let arr = [
0, 1, 2, 3, 4, 5
];
console.log(move(arr, -30, -45));
console.log(move(arr, 0, -45));
console.log(move(arr, 0, 1));
console.log(move(arr, 0, 0));
console.log(move(arr, 5, 5));
Upvotes: 1
Reputation: 579
Just simple mutable solution without any splice
or other array functions with complexity of O(n)
without using any memory allocations.
If the desired index is out of array boundaries, it will move element to end/start of the array.
function moveArray(arr, from, to) {
if (from >= arr.length || from < 0) throw Error('Out of boundaries'); // cover edge cases for moving element
const increment = from > to ? -1 : 1; // where to move others (up/down)
to = increment > 0 ? Math.min(to, arr.length - 1) : Math.max(to, 0); // cover edge cases for placement (start/end)
if (from === to) return;
const target = arr[from];
while (from !== to) arr[from] = arr[(from += increment)]; // move elements in between
arr[to] = target; // place target at desired index
}
// test/example
const arr = [2,3,4,5,6,7,8];
moveArray(arr, 2, 4);
console.log(arr); // [ 2, 3, 5, 6, 4, 7, 8 ]
moveArray(arr, 3, 11);
console.log(arr); // [ 2, 3, 5, 4, 7, 8, 6 ]
moveArray(arr, 6, -1);
console.log(arr); // [ 6, 2, 3, 5, 4, 7, 8 ]
Upvotes: 0
Reputation: 149
In 2022, this typescript utility will work along with a unit test.
export const arrayMove = <T>(arr: T[], fromIndex: number, toIndex: number) => {
const newArr = [...arr];
newArr.splice(toIndex, 0, newArr.splice(fromIndex, 1)[0]);
return newArr;
};
const testArray = ['1', '2', '3', '4'];
describe('arrayMove', () => {
it('should move array item to toIndex', () => {
expect(arrayMove(testArray, 2, 0)).toEqual(['3', '1', '2', '4']);
expect(arrayMove(testArray, 3, 1)).toEqual(['1', '4', '2', '3']);
expect(arrayMove(testArray, 1, 2)).toEqual(['1', '3', '2', '4']);
expect(arrayMove(testArray, 0, 2)).toEqual(['2', '3', '1', '4']);
});
});
Upvotes: 8
Reputation: 464
We can move array element from one position to another position in many ways. Here I try to solve this in 3 ways in immutably.
splice
where time complexity is Quadratic Time - O(n^2)function arrayMove(arr, oldIndex, newIndex) {
const copiedArr = [...arr];
const length = copiedArr.length;
if (oldIndex !== newIndex && length > oldIndex && length > newIndex) {
copiedArr.splice(newIndex, 0, copiedArr.splice(oldIndex, 1)[0]);
}
return copiedArr;
}
arrayMove([1,2,3,4], 0, 3) // [2,3,4,1]
flatMap
where time complexity is Linear Time - O(n)function arrayMove(arr, oldIndex, newIndex) {
const length = arr.length;
const itemToMove = arr[oldIndex]
if (oldIndex === newIndex || oldIndex > length || newIndex > length) {
return arr;
}
return arr.flatMap((item, index) => {
if (index === oldIndex) return [];
if (index === newIndex) return oldIndex < newIndex ? [item, itemToMove] : [itemToMove, item];
return item;
})
}
arrayMove([1,2,3,4], 0, 3) // [2,3,4,1]
reduce
where time complexity is Linear Time - O(n)function arrayMove(arr, oldIndex, newIndex) {
const length = arr.length;
const itemToMove = arr[oldIndex]
if (oldIndex === newIndex || oldIndex > length || newIndex > length) {
return arr;
}
return arr.reduce((acc, item, index) => {
if (index === oldIndex) return acc;
if (index === newIndex) return oldIndex < newIndex ? [...acc, item, itemToMove] : [...acc, itemToMove, item];
return [...acc, item];
}, [])
}
arrayMove([1,2,3,4], 0, 3) // [2,3,4,1]
You can also checkout this gist: Move an array element from one array position to another
Upvotes: 2
Reputation: 3785
Copied from @Merc's answer. I like that one best because it is not creating new arrays and modifies the array in place. All I did was update to ES6 and add the types.
export function moveItemInArray<T>(workArray: T[], fromIndex: number, toIndex: number): T[] {
if (toIndex === fromIndex) {
return workArray;
}
const target = workArray[fromIndex];
const increment = toIndex < fromIndex ? -1 : 1;
for (let k = fromIndex; k !== toIndex; k += increment) {
workArray[k] = workArray[k + increment];
}
workArray[toIndex] = target;
return workArray;
}
Upvotes: 5
Reputation: 65
As with everything, the full use is what matters.
There are perfectly fine answers here for a single move, and for both small and large data sets. If you're doing thousands of moves though, I'd suggest looking at states and less-frequent intensive operations. Something like:
["a", "b", "c"]
would change to
[
{val: 'a', order: 0},
{val: 'b', order: 1},
{val: 'c', order: 2},
]
Then, apply thousands of updates.
Finally, you sort by the "order" variable. And perhaps renumber things too.
I haven't tested the performance of this, but can imagine at a certain level of usage it'd be better than trying to rebuild arrays every 1000s of times.
Upvotes: 0
Reputation: 2927
Object oriented, expressive, debuggable, without mutation, tested.
class Sorter {
sortItem(array, fromIndex, toIndex) {
const reduceItems = () => {
const startingItems = array.slice(0, fromIndex);
const endingItems = array.slice(fromIndex + 1);
return startingItems.concat(endingItems);
}
const addMovingItem = (movingItem, reducedItems) => {
const startingNewItems = reducedItems.slice(0, toIndex);
const endingNewItems = reducedItems.slice(toIndex);
const newItems = startingNewItems.concat([movingItem]).concat(endingNewItems);
return newItems;
}
const movingItem = array[fromIndex];
const reducedItems = reduceItems();
const newItems = addMovingItem(movingItem, reducedItems);
return newItems;
}
}
const sorter = new Sorter();
export default sorter;
import sorter from 'src/common/Sorter';
test('sortItem first item forward', () => {
const startingArray = ['a', 'b', 'c', 'd'];
const expectedArray = ['b', 'a', 'c', 'd'];
expect(sorter.sortItem(startingArray, 0, 1)).toStrictEqual(expectedArray);
});
test('sortItem middle item forward', () => {
const startingArray = ['a', 'b', 'c', 'd'];
const expectedArray = ['a', 'c', 'b', 'd'];
expect(sorter.sortItem(startingArray, 1, 2)).toStrictEqual(expectedArray);
});
test('sortItem middle item backward', () => {
const startingArray = ['a', 'b', 'c', 'd'];
const expectedArray = ['a', 'c', 'b', 'd'];
expect(sorter.sortItem(startingArray, 2, 1)).toStrictEqual(expectedArray);
});
test('sortItem last item backward', () => {
const startingArray = ['a', 'b', 'c', 'd'];
const expectedArray = ['a', 'b', 'd', 'c'];
expect(sorter.sortItem(startingArray, 3, 2)).toStrictEqual(expectedArray);
});
Upvotes: 1
Reputation: 2257
I resolved my issue using immutability-helper
library.
import update from 'immutability-helper';
const move = (arr: any[], from: number, to: number) => update(arr, {
$splice: [
[from, 1],
[to, 0, arr[from] as string],
],
});
const testArray = ['a', 'b', 'c', 'd', 'e'];
console.log(move(testArray, 1, 3)); // [ 'c', 'b', 'c', 'd', 'e' ]
console.log(move(testArray, 4, 0)); // [ 'e', 'b', 'c', 'd', 'a' ]
Upvotes: 0
Reputation: 8674
I ended up combining two of these to work a little better when moving both small and large distances. I get fairly consistent results, but this could probably be tweaked a little bit by someone smarter than me to work differently for different sizes, etc.
Using some of the other methods when moving objects small distances was significantly faster (x10) than using splice. This might change depending on the array lengths though, but it is true for large arrays.
function ArrayMove(array, from, to) {
if ( Math.abs(from - to) > 60) {
array.splice(to, 0, array.splice(from, 1)[0]);
} else {
// works better when we are not moving things very far
var target = array[from];
var inc = (to - from) / Math.abs(to - from);
var current = from;
for (; current != to; current += inc) {
array[current] = array[current + inc];
}
array[to] = target;
}
}
https://web.archive.org/web/20181026015711/https://jsperf.com/arraymove-many-sizes
Upvotes: 2
Reputation: 107
You can implement some basic calculus and create a universal function for moving array elements from one position to the other.
For JavaScript it looks like this:
function magicFunction (targetArray, indexFrom, indexTo) {
targetElement = targetArray[indexFrom];
magicIncrement = (indexTo - indexFrom) / Math.abs (indexTo - indexFrom);
for (Element = indexFrom; Element != indexTo; Element += magicIncrement){
targetArray[Element] = targetArray[Element + magicIncrement];
}
targetArray[indexTo] = targetElement;
}
Check out "moving array elements" at "Gloommatter" for detailed explanation.
Upvotes: 10
Reputation: 12829
Here's a one liner I found on JSPerf....
Array.prototype.move = function(from, to) {
this.splice(to, 0, this.splice(from, 1)[0]);
};
which is awesome to read, but if you want performance (in small data sets) try...
Array.prototype.move2 = function(pos1, pos2) {
// local variables
var i, tmp;
// cast input parameters to integers
pos1 = parseInt(pos1, 10);
pos2 = parseInt(pos2, 10);
// if positions are different and inside array
if (pos1 !== pos2 && 0 <= pos1 && pos1 <= this.length && 0 <= pos2 && pos2 <= this.length) {
// save element from position 1
tmp = this[pos1];
// move element down and shift other elements up
if (pos1 < pos2) {
for (i = pos1; i < pos2; i++) {
this[i] = this[i + 1];
}
}
// move element up and shift other elements down
else {
for (i = pos1; i > pos2; i--) {
this[i] = this[i - 1];
}
}
// put element from position 1 to destination
this[pos2] = tmp;
}
}
I can't take any credit, it should all go to Richard Scarrott. It beats the splice based method for smaller data sets in this performance test. It is however significantly slower on larger data sets as Darwayne points out.
Upvotes: 335
Reputation: 157
I love immutable, functional one liners :) ...
const swapIndex = (array, from, to) => (
from < to
? [...array.slice(0, from), ...array.slice(from + 1, to + 1), array[from], ...array.slice(to + 1)]
: [...array.slice(0, to), array[from], ...array.slice(to, from), ...array.slice(from + 1)]
);
Upvotes: 2
Reputation: 2955
Here's one way to do it in an immutable way. It handles negative numbers as well as an added bonus. This is reduces number of possible bugs at the cost of performance compared to editing the original array.
const numbers = [1, 2, 3];
const moveElement = (array, from, to) => {
const copy = [...array];
const valueToMove = copy.splice(from, 1)[0];
copy.splice(to, 0, valueToMove);
return copy;
};
console.log(moveElement(numbers, 0, 2))
// > [2, 3, 1]
console.log(moveElement(numbers, -1, -3))
// > [3, 1, 2]
Upvotes: 4
Reputation: 779
Here is my one liner ES6 solution with an optional parameter on
.
if (typeof Array.prototype.move === "undefined") {
Array.prototype.move = function(from, to, on = 1) {
this.splice(to, 0, ...this.splice(from, on))
}
}
Adaptation of the first solution proposed by digiguru
The parameter on
is the number of element starting from from
you want to move.
Here is a chainable variation of this:
if (typeof Array.prototype.move === "undefined") {
Array.prototype.move = function(from, to, on = 1) {
return this.splice(to, 0, ...this.splice(from, on)), this
}
}
[3, 4, 5, 1, 2].move(3, 0, 2) // => [1, 2, 3, 4, 5]
If you'd like to avoid prototype pollution, here's a stand-alone function:
function move(array, from, to, on = 1) {
return array.splice(to, 0, ...array.splice(from, on)), array
}
move([3, 4, 5, 1, 2], 3, 0, 2) // => [1, 2, 3, 4, 5]
And finally, here's a pure function that doesn't mutate the original array:
function moved(array, from, to, on = 1) {
return array = array.slice(), array.splice(to, 0, ...array.splice(from, on)), array
}
This should cover basically every variation seen in every other answer.
Upvotes: 28
Reputation: 233
If the object is nested:
let array = ['a', 'b', 'c', 'd', 'e'];
let existingElement = JSON.parse(JSON.stringify(array[3]));
array.splice(1, 0, existingElement);
array.splice(4, 1);
console.log(array)
Upvotes: 0
Reputation: 25956
In your example, because is an array of string
we can use a ranking object to reorder the string array:
let rank = { 'a': 0, 'b': 1, 'c': 2, 'd': 0.5, 'e': 4 };
arr.sort( (i, j) => rank[i] - rank[j] );
We can use this approach to write a move
function that works on a string array:
function stringArrayMove(arr, from, to)
{
let rank = arr.reduce( (p, c, i) => ( p[c] = i, p ), ({ }) );
// rank = { 'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4 }
rank[arr[from]] = to - 0.5;
// rank = { 'a': 0, 'b': 1, 'c': 2, 'd': 1.5, 'e': 4 }
arr.sort( (i, j) => rank[i] - rank[j] );
// arr = [ 'a', 'd', 'b', 'c', 'e' ];
}
let arr = [ 'a', 'b', 'c', 'd', 'e' ];
stringArrayMove(arr, 3, 1);
console.log( JSON.stringify(arr) );
If, however, the thing we wanted to sort is an array of object, we can introduce the ranking as a new property of each object, i.e.
let arr = [ { value: 'a', rank: 0 },
{ value: 'b', rank: 1 },
{ value: 'c', rank: 2 },
{ value: 'd', rank: 0.5 },
{ value: 'e', rank: 4 } ];
arr.sort( (i, j) => i['rank'] - j['rank'] );
We can use Symbol
to hide the visibility of this property, i.e. it will not be shown in JSON.stringify
. We can generalize this in an objectArrayMove
function:
function objectArrayMove(arr, from, to) {
let rank = Symbol("rank");
arr.forEach( (item, i) => item[rank] = i );
arr[from][rank] = to - 0.5;
arr.sort( (i, j) => i[rank] - j[rank]);
}
let arr = [ { value: 'a' }, { value: 'b' }, { value: 'c' }, { value: 'd' }, { value: 'e' } ];
console.log( 'array before move: ', JSON.stringify( arr ) );
// array before move: [{"value":"a"},{"value":"b"},{"value":"c"},{"value":"d"},{"value":"e"}]
objectArrayMove(arr, 3, 1);
console.log( 'array after move: ', JSON.stringify( arr ) );
// array after move: [{"value":"a"},{"value":"d"},{"value":"b"},{"value":"c"},{"value":"e"}]
Upvotes: 0
Reputation: 61
This is a really simple method using splice
Array.prototype.moveToStart = function(index) {
this.splice(0, 0, this.splice(index, 1)[0]);
return this;
};
Upvotes: 2
Reputation: 8670
This method will preserve the original array, and check for bounding errors.
const move = (from, to, arr) => {
to = Math.max(to,0)
from > to
? [].concat(
arr.slice(0,to),
arr[from],
arr.filter((x,i) => i != from).slice(to))
: to > from
? [].concat(
arr.slice(0, from),
arr.slice(from + 1, to + 1),
arr[from],
arr.slice(to + 1))
: arr}
Upvotes: 0
Reputation: 169
Another pure JS variant using ES6 array spread operator with no mutation
const reorder = (array, sourceIndex, destinationIndex) => {
const smallerIndex = Math.min(sourceIndex, destinationIndex);
const largerIndex = Math.max(sourceIndex, destinationIndex);
return [
...array.slice(0, smallerIndex),
...(sourceIndex < destinationIndex
? array.slice(smallerIndex + 1, largerIndex + 1)
: []),
array[sourceIndex],
...(sourceIndex > destinationIndex
? array.slice(smallerIndex, largerIndex)
: []),
...array.slice(largerIndex + 1),
];
}
// returns ['a', 'c', 'd', 'e', 'b', 'f']
console.log(reorder(['a', 'b', 'c', 'd', 'e', 'f'], 1, 4))
Upvotes: 6
Reputation: 603
I think the best way is define a new property for Arrays
Object.defineProperty(Array.prototype, 'move', {
value: function (old_index, new_index) {
while (old_index < 0) {
old_index += this.length;
}
while (new_index < 0) {
new_index += this.length;
}
if (new_index >= this.length) {
let k = new_index - this.length;
while ((k--) + 1) {
this.push(undefined);
}
}
this.splice(new_index, 0, this.splice(old_index, 1)[0]);
return this;
}
});
console.log([10, 20, 30, 40, 50].move(0, 1)); // [20, 10, 30, 40, 50]
console.log([10, 20, 30, 40, 50].move(0, 2)); // [20, 30, 10, 40, 50]
Upvotes: 0
Reputation: 26
Immutable version without array copy:
const moveInArray = (arr, fromIndex, toIndex) => {
if (toIndex === fromIndex || toIndex >= arr.length) return arr;
const toMove = arr[fromIndex];
const movedForward = fromIndex < toIndex;
return arr.reduce((res, next, index) => {
if (index === fromIndex) return res;
if (index === toIndex) return res.concat(
movedForward ? [next, toMove] : [toMove, next]
);
return res.concat(next);
}, []);
};
Upvotes: 0
Reputation: 205
I thought this was a swap problem but it's not. Here's my one-liner solution:
const move = (arr, from, to) => arr.map((item, i) => i === to ? arr[from] : (i >= Math.min(from, to) && i <= Math.max(from, to) ? arr[i + Math.sign(to - from)] : item));
Here's a small test:
let test = ['a', 'b', 'c', 'd', 'e'];
console.log(move(test, 0, 2)); // [ 'b', 'c', 'a', 'd', 'e' ]
console.log(move(test, 1, 3)); // [ 'a', 'c', 'd', 'b', 'e' ]
console.log(move(test, 2, 4)); // [ 'a', 'b', 'd', 'e', 'c' ]
console.log(move(test, 2, 0)); // [ 'c', 'a', 'b', 'd', 'e' ]
console.log(move(test, 3, 1)); // [ 'a', 'd', 'b', 'c', 'e' ]
console.log(move(test, 4, 2)); // [ 'a', 'b', 'e', 'c', 'd' ]
console.log(move(test, 4, 0)); // [ 'e', 'a', 'b', 'c', 'd' ]
Upvotes: 2
Reputation: 449
var ELEMS = ['a', 'b', 'c', 'd', 'e'];
/*
Source item will remove and it will be placed just after destination
*/
function moveItemTo(sourceItem, destItem, elements) {
var sourceIndex = elements.indexOf(sourceItem);
var destIndex = elements.indexOf(destItem);
if (sourceIndex >= -1 && destIndex > -1) {
elements.splice(destIndex, 0, elements.splice(sourceIndex, 1)[0]);
}
return elements;
}
console.log('Init: ', ELEMS);
var result = moveItemTo('a', 'c', ELEMS);
console.log('BeforeAfter: ', result);
Upvotes: 0