795

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.

3
  • using ES6 const changeValuePosition = (arr, init, target) => {[arr[init],arr[target]] = [arr[target],arr[init]]; return arr} Commented May 18, 2020 at 3:24
  • 5
    That just swaps the elements at init and target.
    – user4945014
    Commented May 24, 2020 at 18:49
  • @user4945014 It's not just swapping. If a swap occurs, the OP would get array = ['a', 'd', 'c', 'b', 'e'], which would mean 'c' and 'b' would be in the wrong order. An insert and shift as he's looking for will keep 'b' and 'c' in the same order.
    – John
    Commented May 1, 2022 at 17:41

49 Answers 49

902
+150

If you'd like a version on npm, array-move is the closest to this answer, although it's not the same implementation. See its usage section for more details. The previous version of this answer (that modified Array.prototype.move) can be found on npm at array.prototype.move.


I had fairly good success with this function:

function array_move(arr, old_index, new_index) {
    if (new_index >= arr.length) {
        var k = new_index - arr.length + 1;
        while (k--) {
            arr.push(undefined);
        }
    }
    arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
    return arr; // for testing
};

// returns [2, 1, 3]
console.log(array_move([1, 2, 3], 0, 1)); 

Note that the last return is simply for testing purposes: splice performs operations on the array in-place, so a return is not necessary. By extension, this move is an in-place operation. If you want to avoid that and return a copy, use slice.

Stepping through the code:

  1. If new_index is greater than the length of the array, we want (I presume) to pad the array properly with new undefineds. This little snippet handles this by pushing undefined on the array until we have the proper length.
  2. Then, in arr.splice(old_index, 1)[0], we splice out the old element. splice returns the element that was spliced out, but it's in an array. In our above example, this was [1]. So we take the first index of that array to get the raw 1 there.
  3. Then we use splice to insert this element in the new_index's place. Since we padded the array above if new_index > arr.length, it will probably appear in the right place, unless they've done something strange like pass in a negative number.

A fancier version to account for negative indices:

function array_move(arr, old_index, new_index) {
    while (old_index < 0) {
        old_index += arr.length;
    }
    while (new_index < 0) {
        new_index += arr.length;
    }
    if (new_index >= arr.length) {
        var k = new_index - arr.length + 1;
        while (k--) {
            arr.push(undefined);
        }
    }
    arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
    return arr; // for testing purposes
};
    
// returns [1, 3, 2]
console.log(array_move([1, 2, 3], -1, -2));

Which should account for things like array_move([1, 2, 3], -1, -2) properly (move the last element to the second to last place). Result for that should be [1, 3, 2].

Either way, in your original question, you would do array_move(arr, 0, 2) for a after c. For d before b, you would do array_move(arr, 3, 1).

15
  • 28
    This works perfectly! And your explanation is very clear. Thanks for taking the time to write this up.
    – Mark Brown
    Commented Mar 15, 2011 at 4:31
  • 18
    You shouldn't manipulate Object and Array prototypes, it causes problems when iterating elements.
    – burak emre
    Commented Mar 19, 2013 at 1:38
  • 10
    @burakemre: I think that conclusion is not so clearly reached. Most good JS programmers (and most popular libraries) will use a .hasOwnProperty check when iterating with things like for..in, especially with libraries like Prototype and MooTools which modify prototypes. Anyway, I didn't feel it was a particularly important issue in a relatively limited example like this, and there is a nice split in the community over whether or not prototype modification is a good idea. Normally, iteration problems are the least concern, though.
    – Reid
    Commented Mar 19, 2013 at 3:24
  • 3
    There is no need for the loop in step 1, you can simply use this[new_index] = undefined; within the if block. As Javascript arrays are sparse this will extend the array size to include the new_index for the .splice to work but without needing to create any intervening elements.
    – Rebecka
    Commented Jul 3, 2015 at 11:23
  • 4
    PLEASE PLEASE don't add this to the prototype. When the TC39 wants to add it natively to JavaScript they will have to use a different, awkward name because of people doing this.
    – jayphelps
    Commented Jan 5, 2017 at 3:46
567

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.

Run Snippet in jsFiddle

13
  • 58
    Since Array.splice returns the removed value(s) in a new Array, you can write it as a one liner... arr.splice(index + 1, 0, arr.splice(index, 1)[0]);
    – Eric Rini
    Commented Feb 3, 2014 at 19:20
  • 148
    Personally I prefer the 3 line code. It's easier to understand: Get a copy of the element; remove it from the array; insert it in a new position. The one liner is shorter but not so clear for other people to understand...
    – Philipp
    Commented Jan 3, 2018 at 22:15
  • 9
    Short and simple code. But it's 2019!!, Create a clone of the array and return it instead of mutating the array. This will make your function "arraymove" comply to functional programming standards Commented Aug 4, 2019 at 16:10
  • 16
    I would have never imagine that for some people, after 2019, mutating an array in place would have become out of fashion. Perfectly legit answer, +1. Commented Mar 27, 2021 at 12:16
  • 28
    It's 2021. There are still cases where copying is totally inappropriate for memory/performance reasons. Pure functions should be the default, but it shouldn't be dogma. Commented Apr 10, 2021 at 14:14
335

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.

12
  • 2
    Your more performant solution is slower on large datasets. jsperf.com/array-prototype-move/8
    – Darwayne
    Commented May 20, 2013 at 11:55
  • 61
    This seems like a really silly tradeof. Performance on small data sets is a negligible gain, but loss on large data sets is a significant loss. Your net exchange is negative.
    – Kyeotic
    Commented Nov 6, 2013 at 2:30
  • 3
    @Reid That wasn't a requirement. IMO it is okay to assume that the length of the array doesn't get modified.
    – robsch
    Commented Mar 8, 2016 at 10:57
  • 4
    One line solution need to handle two situations: from >= to ? this.splice(to, 0, this.splice(from, 1)[0]) : this.splice(to - 1, 0, this.splice(from, 1)[0]);
    – Rob Lao
    Commented Jul 7, 2017 at 1:42
  • 25
    Please never modify builtin prototypes, ever. nczonline.net/blog/2010/03/02/…
    – LJHarb
    Commented Jan 4, 2018 at 3:17
48

The splice() method adds/removes items to/from an array, and returns the removed item(s).

Note: This method changes the original array. /w3schools/

Array.prototype.move = function(from,to){
  this.splice(to,0,this.splice(from,1)[0]);
  return this;
};

var arr = [ 'a', 'b', 'c', 'd', 'e'];
arr.move(3,1);//["a", "d", "b", "c", "e"]


var arr = [ 'a', 'b', 'c', 'd', 'e'];
arr.move(0,2);//["b", "c", "a", "d", "e"]

as the function is chainable this works too:

alert(arr.move(0,2).join(','));

demo here

1
  • 6
    See other comments about this: it's a bad idea to modify built-in prototypes like Array and Object. You will break things.
    – geoidesic
    Commented Jan 29, 2018 at 19:10
38

My 2c. Easy to read, it works, it's fast, it doesn't create new arrays.

function move(array, from, to) {
  if( to === from ) return array;

  var target = array[from];                         
  var increment = to < from ? -1 : 1;

  for(var k = from; k != to; k += increment){
    array[k] = array[k + increment];
  }
  array[to] = target;
  return array;
}
4
  • 2
    At first string of function you should return array, as it done at the end. Commented Mar 31, 2017 at 8:11
  • 3
    True how did i miss that? Fixed!
    – Merc
    Commented Mar 31, 2017 at 11:53
  • I like your simple and flexible solution the most. Thx! Commented Nov 6, 2017 at 16:44
  • TypeScript coders, scroll down to find my answer which is based upon this excellent answer by @Merc, that I remixed with added types. Commented Sep 15, 2022 at 14:19
29

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.

4
  • 2
    The solution is fine. However, when you expand a prototype you shouldn't use arrow function because in this case 'this' is not an array instance but for example Window object.
    – wawka
    Commented Jun 23, 2018 at 6:40
  • 2
    This is my favorite answer - modern, concise and simple. I added a chainable variation, to be more consistent with the standard Array methods. Some folks are going to take issue with prototype pollution, so I added a stand-alone function as well. Finally, some use-cases require a pure function, as opposed to in-place manipulation, so I added that as well. Commented Apr 11, 2021 at 9:32
  • 4
    I couldn't understand the last pure function. Is that statement comma separated? How does it work?
    – Debmallya
    Commented Aug 6, 2021 at 15:23
  • 2
    @batbrain9392 - Check this question: stackoverflow.com/q/10284536/1914985
    – Chique
    Commented Sep 9, 2021 at 22:55
21

Got this idea from @Reid of pushing something in the place of the item that is supposed to be moved to keep the array size constant. That does simplify calculations. Also, pushing an empty object has the added benefits of being able to search for it uniquely later on. This works because two objects are not equal until they are referring to the same object.

({}) == ({}); // false

So here's the function which takes in the source array, and the source, destination indexes. You could add it to the Array.prototype if needed.

function moveObjectAtIndex(array, sourceIndex, destIndex) {
    var placeholder = {};
    // remove the object from its initial position and
    // plant the placeholder object in its place to
    // keep the array length constant
    var objectToMove = array.splice(sourceIndex, 1, placeholder)[0];
    // place the object in the desired position
    array.splice(destIndex, 0, objectToMove);
    // take out the temporary object
    array.splice(array.indexOf(placeholder), 1);
}
4
  • 1
    This looks promising...and I didn't know that about javascript js comparisons. Thanks!
    – Mark Brown
    Commented Mar 15, 2011 at 4:32
  • Does't works for case sourceIndex = 0, destIndex = 1 Commented Mar 31, 2017 at 8:02
  • destIndex is meant to be the index before the source element is moved in the array.
    – Anurag
    Commented Apr 17, 2017 at 5:04
  • This is the best answer so far. Other answers failed a couple of unit tests in my suite (moving object forward) Commented Mar 15, 2019 at 13:56
16

This is based on @Reid's solution. Except:

  • I'm not changing the Array prototype.
  • Moving an item out of bounds to the right does not create undefined items, it just moves the item to the right-most position.

Function:

function move(array, oldIndex, newIndex) {
    if (newIndex >= array.length) {
        newIndex = array.length - 1;
    }
    array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]);
    return array;
}

Unit tests:

describe('ArrayHelper', function () {
    it('Move right', function () {
        let array = [1, 2, 3];
        arrayHelper.move(array, 0, 1);
        assert.equal(array[0], 2);
        assert.equal(array[1], 1);
        assert.equal(array[2], 3);
    })
    it('Move left', function () {
        let array = [1, 2, 3];
        arrayHelper.move(array, 1, 0);
        assert.equal(array[0], 2);
        assert.equal(array[1], 1);
        assert.equal(array[2], 3);
    });
    it('Move out of bounds to the left', function () {
        let array = [1, 2, 3];
        arrayHelper.move(array, 1, -2);
        assert.equal(array[0], 2);
        assert.equal(array[1], 1);
        assert.equal(array[2], 3);
    });
    it('Move out of bounds to the right', function () {
        let array = [1, 2, 3];
        arrayHelper.move(array, 1, 4);
        assert.equal(array[0], 1);
        assert.equal(array[1], 3);
        assert.equal(array[2], 2);
    });
});
3
  • 1
    this is wrong, if you insert a post position, the index will change since you have remove the item
    – Yao Zhao
    Commented May 13, 2016 at 13:14
  • Thank you. I wanted to remove an item from an array without leaving a null element (which occured when using splice(indexToRemove). I used your method to move the item I wanted to remove to the end of the array, and then used the pop() method to remove. Commented Jul 9, 2016 at 13:12
  • liked "move the item to the right-most position" feature, useful for my case. thx
    – bFunc
    Commented Aug 20, 2018 at 13:17
10

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.

https://web.archive.org/web/20121105042534/http://www.gloommatter.com:80/DDesign/programming/moving-any-array-elements-universal-function.html

3
  • 2
    This ought to be the correct answer, as it does not allocate any new arrays. Thanks!
    – Cᴏʀʏ
    Commented Aug 4, 2013 at 0:56
  • The link is broken.
    – Rokit
    Commented Feb 6, 2020 at 17:39
  • Solution is great! I loved this solution as it does not use splice function but does not work for negative indexes and also does not check for array outbound. Commented Oct 17, 2021 at 13:41
9

I've implemented an immutable ECMAScript 6 solution based off of @Merc's answer over here:

const moveItemInArrayFromIndexToIndex = (array, fromIndex, toIndex) => {
  if (fromIndex === toIndex) return array;

  const newArray = [...array];

  const target = newArray[fromIndex];
  const inc = toIndex < fromIndex ? -1 : 1;

  for (let i = fromIndex; i !== toIndex; i += inc) {
    newArray[i] = newArray[i + inc];
  }

  newArray[toIndex] = target;

  return newArray;
};

The variable names can be shortened, just used long ones so that the code can explain itself.

2
  • definitely a better answer, mutations creates side effects
    – Matt Lo
    Commented Oct 19, 2018 at 4:30
  • 1
    Out of curiosity, why not just return array immediately if fromIndex === toIndex, and only create the newArray if it's not the case? Immutability doesn't mean that one fresh copy must be created per function call even when there's no change. Just asking b/c the motive for the increased length of this function (relative to splice based one-liners) is performance, and fromIndex may well often equal toIndex, depending on the usage. Commented Nov 1, 2018 at 19:49
8

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']);
  });
});

8

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

6

One approach would be to create a new array with the pieces in the order you want, using the slice method.

Example

var arr = [ 'a', 'b', 'c', 'd', 'e'];
var arr2 = arr.slice(0,1).concat( ['d'] ).concat( arr.slice(2,4) ).concat( arr.slice(4) );
  • arr.slice(0,1) gives you ['a']
  • arr.slice(2,4) gives you ['b', 'c']
  • arr.slice(4) gives you ['e']
1
  • 1
    You do realize that your arr2 ends up being a string due to the concatenation operations, right? :) It ends up being "adc,de". Commented Mar 15, 2011 at 2:07
6

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))
      
 

3
  • I personally loved this and with the next search will run across this again ... so adding my own personalized implementation ... 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)] ); Commented Oct 14, 2021 at 16:30
  • This answer should be higher thanks to the purity.
    – mjrdnk
    Commented Feb 7, 2023 at 22:54
  • This answer is amazing. Non-mutative, and it does boundary checks elegantly. However, there's a bug. If you define a sourceIndex that does not exist in the array, you end up with an array 1 size larger than the original that includes an undefined value. Eg [1,2,3,4,5] becomes [undefined,1,2,3,4,5] Commented Feb 21, 2023 at 8:46
5

The splice method of Array might help: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice

Just keep in mind it might be relatively expensive since it has to actively re-index the array.

2
  • Yep, but as soon as I perform the splice, the array indices are updated, which makes it difficult for me to figure out where to place the element that I just removed. Especially since I need the function to be able to handle moves in both directions.
    – Mark Brown
    Commented Mar 15, 2011 at 2:12
  • @Mark: don't splice the string and save it into the same variable, make a new string and splice that. See my answer below. Commented Mar 15, 2011 at 16:04
5

I needed an immutable move method (one that didn't change the original array), so I adapted @Reid's accepted answer to simply use Object.assign to create a copy of the array before doing the splice.

Array.prototype.immutableMove = function (old_index, new_index) {
  var copy = Object.assign([], this);
  if (new_index >= copy.length) {
      var k = new_index - copy.length;
      while ((k--) + 1) {
          copy.push(undefined);
      }
  }
  copy.splice(new_index, 0, copy.splice(old_index, 1)[0]);
  return copy;
};

Here is a jsfiddle showing it in action.

1
  • It's always good to see ppl taking mutations into considerations. Commented Aug 14, 2017 at 11:22
5

TypeScript Version

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;
}
1
  • 1
    Love this one -- thanks!
    – Merc
    Commented Oct 6, 2022 at 11:02
4
    Array.prototype.moveUp = function (value, by) {
        var index = this.indexOf(value),
            newPos = index - (by || 1);

        if (index === -1)
            throw new Error("Element not found in array");

        if (newPos < 0)
            newPos = 0;

        this.splice(index, 1);
        this.splice(newPos, 0, value);
    };

    Array.prototype.moveDown = function (value, by) {
        var index = this.indexOf(value),
            newPos = index + (by || 1);

        if (index === -1)
            throw new Error("Element not found in array");

        if (newPos >= this.length)
            newPos = this.length;

        this.splice(index, 1);
        this.splice(newPos, 0, value);
    };



    var arr = ['banana', 'curyWurst', 'pc', 'remembaHaruMembaru'];

    alert('withiout changes= '+arr[0]+' ||| '+arr[1]+' ||| '+arr[2]+' ||| '+arr[3]);
    arr.moveDown(arr[2]);


    alert('third word moved down= '+arr[0] + ' ||| ' + arr[1] + ' ||| ' + arr[2] + ' ||| ' + arr[3]);
    arr.moveUp(arr[2]);
    alert('third word moved up= '+arr[0] + ' ||| ' + arr[1] + ' ||| ' + arr[2] + ' ||| ' + arr[3]);

http://plnkr.co/edit/JaiAaO7FQcdPGPY6G337?p=preview

4

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] 
4

Find and move an element from "n"th position to 0th position.

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

2

It is stated in many places (adding custom functions into Array.prototype) playing with the Array prototype could be a bad idea, anyway I combined the best from various posts, I came with this, using modern Javascript:

    Object.defineProperty(Array.prototype, 'immutableMove', {
        enumerable: false,
        value: function (old_index, new_index) {
            var copy = Object.assign([], this)
            if (new_index >= copy.length) {
                var k = new_index - copy.length;
                while ((k--) + 1) { copy.push(undefined); }
            }
            copy.splice(new_index, 0, copy.splice(old_index, 1)[0]);
            return copy
        }
    });

    //how to use it
    myArray=[0, 1, 2, 3, 4];
    myArray=myArray.immutableMove(2, 4);
    console.log(myArray);
    //result: 0, 1, 3, 4, 2

Hope can be useful to anyone

2

This version isn't ideal for all purposes, and not everyone likes comma expressions, but here's a one-liner that's a pure expression, creating a fresh copy:

const move = (from, to, ...a) => (a.splice(to, 0, ...a.splice(from, 1)), a)

A slightly performance-improved version returns the input array if no move is needed, it's still OK for immutable use, as the array won't change, and it's still a pure expression:

const move = (from, to, ...a) => 
    from === to 
    ? a 
    : (a.splice(to, 0, ...a.splice(from, 1)), a)

The invocation of either is

const shuffled = move(fromIndex, toIndex, ...list)

i.e. it relies on spreading to generate a fresh copy. Using a fixed arity 3 move would jeopardize either the single expression property, or the non-destructive nature, or the performance benefit of splice. Again, it's more of an example that meets some criteria than a suggestion for production use.

2

const move = (from, to, ...a) =>from === to ? a : (a.splice(to, 0, ...a.splice(from, 1)), a);
const moved = move(0, 2, ...['a', 'b', 'c']);
console.log(moved)

2

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' ]
2
  • Well, the question was not about swapping items. The author asked for a solution for an insert strategy. Commented Mar 30, 2020 at 10:09
  • With regard to the question at hand, this is objectively the wrong answer. Commented Apr 7, 2020 at 15:06
2

This is a really simple method using splice

Array.prototype.moveToStart = function(index) {
    this.splice(0, 0, this.splice(index, 1)[0]);
    return this;
  };
2

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)]
);
2
  • This is perfect. Thank you! Commented Dec 5, 2021 at 12:13
  • To the untrained eye, this is absolutely horrendously complicated. Why are people obsessed with one liners these days? It's not like you can't afford to use some whitespace...
    – user678415
    Commented Aug 9, 2022 at 11:56
2

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

2

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.

Move array element using 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]

Move array element using 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]

Move array element using 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

1
  • Why do you spread the accumulated array several times in the reducer loop? Simply use push? I'm just wondering why you see the need to copy the array inside the loop.
    – Risadinha
    Commented Nov 23, 2023 at 13:31
1

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

@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));

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.