90

I was wondering what was the most efficient way to rotate a JavaScript array.

I came up with this solution, where a positive n rotates the array to the right, and a negative n to the left (-length < n < length) :

Array.prototype.rotateRight = function( n ) {
  this.unshift( this.splice( n, this.length ) );
}

Which can then be used this way:

var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
months.rotate( new Date().getMonth() );

My original version above has a flaw, as pointed out by Christoph in the comments bellow, a correct version is (the additional return allows chaining):

Array.prototype.rotateRight = function( n ) {
  this.unshift.apply( this, this.splice( n, this.length ) );
  return this;
}

Is there a more compact and/or faster solution, possibly in the context of a JavaScript framework? (none of the proposed versions bellow is either more compact or faster)

Is there any JavaScript framework out there with an array rotate built-in? (Still not answered by anyone)

Ivar
  • 4,655
  • 12
  • 45
  • 50
Jean Vincent
  • 10,375
  • 5
  • 30
  • 24
  • 1
    I don’t get what your example should do. Why don’t you just use `months[new Date().getMonth()]` to get the name of the current month? – Gumbo Dec 31 '09 at 12:59
  • 1
    @Jean: the code is broken: the way you do it, it'll unshift the spliced elements as an array and not individually; you'll have to use `apply()` to make your implementation work – Christoph Dec 31 '09 at 13:24
  • Today the rotation would modify months to show this list (with `Dec` in the first position): `["Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"]` – Jean Vincent Dec 31 '09 at 13:24
  • @Christoph, you are right, this would not work as a generic rotate function. It only works if used to convert to a string right after. – Jean Vincent Dec 31 '09 at 18:15
  • @Jean: you can fix your version via `Array.prototype.unshift.apply(this, this.splice(...))` - my version does the same thing, but uses `push()` instead of `unshift()` – Christoph Dec 31 '09 at 19:00
  • @Christoph, thanks. I don't know if I should fix the original (I'm new to stack overflow), because I'd like you to keep the credit for finding the flaw and providing the right solution. – Jean Vincent Dec 31 '09 at 19:50
  • @Christoph, could you answer my questions regarding performance and Javascript frameworks? – Jean Vincent Dec 31 '09 at 19:51
  • @Christoph, using unshift() versus push() enables to use splice( n, length ) where n can use negative numbers without converting n to a positive number first. This is the reason I used unshift() in the first place. Running tests on Firefox 3.5 and IE8 showed that the resulting one liner is faster. Also, I did not find any performance improvement or penalty with the method caching plus closure. So I don't think this is worth loosing overloading capabilities. – Jean Vincent Dec 31 '09 at 21:15
  • @Jean: but if you use an overloading version, you'll lose the ability to call `rotate` on array-like objects (eg `arguments`) which don't implement their own versions of the array methods (see my edited answer for a work-around); also, my conversion of `count` makes it possible to supply any integer as argument and not just the ones from `-len..len`; granted, using `unshift()` moves parts of the conversion from JS to native code inside `splice()`, but I don't know how large the performance impact actually is; I'm too lazy to do benchmarks now, perhaps I'll do some next year ;) – Christoph Dec 31 '09 at 22:29
  • ok, I lied: got curious and did some benchmarks; see my answer... – Christoph Dec 31 '09 at 22:45

35 Answers35

153

You can use push(), pop(), shift() and unshift() methods:

function arrayRotate(arr, reverse) {
  if (reverse) arr.unshift(arr.pop());
  else arr.push(arr.shift());
  return arr;
}

usage:

arrayRotate([1, 2, 3, 4, 5]);       // [2, 3, 4, 5, 1];
arrayRotate([1, 2, 3, 4, 5], true); // [5, 1, 2, 3, 4];

If you need count argument see my other answer:
https://stackoverflow.com/a/33451102

Yukulélé
  • 11,464
  • 8
  • 52
  • 76
  • It doesn't have a count although that's not a huge deal depending on the use. – JustGage Apr 21 '15 at 16:18
  • @JustGage, I publish another answer with count support https://stackoverflow.com/questions/1985260/javascript-array-rotate#33451102 – Yukulélé Oct 31 '15 at 11:40
  • 3
    Just a note: beware that this method is intrusive and it will manipulate the array sent as the param. Not difficult to solve [duplicating the array before send it](http://stackoverflow.com/questions/3978492/javascript-fastest-way-to-duplicate-an-array-slice-vs-for-loop) – fguillen Jul 14 '16 at 09:46
  • Here is a version in typescript with left right variable https://stackblitz.com/edit/typescript-vtcmhp – Dživo Jelić Mar 21 '19 at 22:52
65

Type-safe, generic version which mutates the array:

Array.prototype.rotate = (function() {
    // save references to array functions to make lookup faster
    var push = Array.prototype.push,
        splice = Array.prototype.splice;

    return function(count) {
        var len = this.length >>> 0, // convert to uint
            count = count >> 0; // convert to int

        // convert count to value in range [0, len)
        count = ((count % len) + len) % len;

        // use splice.call() instead of this.splice() to make function generic
        push.apply(this, splice.call(this, 0, count));
        return this;
    };
})();

In the comments, Jean raised the issue that the code doesn't support overloading of push() and splice(). I don't think this is really useful (see comments), but a quick solution (somewhat of a hack, though) would be to replace the line

push.apply(this, splice.call(this, 0, count));

with this one:

(this.push || push).apply(this, (this.splice || splice).call(this, 0, count));

Using unshift() instead of push() is nearly twice as fast in Opera 10, whereas the differences in FF were negligible; the code:

Array.prototype.rotate = (function() {
    var unshift = Array.prototype.unshift,
        splice = Array.prototype.splice;

    return function(count) {
        var len = this.length >>> 0,
            count = count >> 0;

        unshift.apply(this, splice.call(this, count % len, len));
        return this;
    };
})();
Marco Bonelli
  • 48,251
  • 16
  • 95
  • 101
Christoph
  • 149,808
  • 36
  • 172
  • 230
  • 2
    Nice caching of `Array.prototype` methods! +1 – James Dec 31 '09 at 14:17
  • Very nice bullet proof implementation but in general I would prefer to rely on exceptions, or just plain bad responses, for bad usage. This to keep the code clean and fast. It's the responsibility of the user to pass correct parameters or pay the consequences. I don't like the penalty for good users. Other than that this is perfect because it does modify the Array as requested and it does not suffer from the flaw in my implementation which unshifted an array instead of the individual elements as you noted. Returning this is also better to allow chaining. So thanks. – Jean Vincent Dec 31 '09 at 18:25
  • What is the cost of the closure here, just to cache push and splice? – Jean Vincent Dec 31 '09 at 18:29
  • @Jean: well, closures captures the whole scope chain; as long as the outer function is top-level, the impact should be negligible and it's O(1) anyway, whereas looking up the Array methods is O(n) in the number of invocations; optimizing implementations might inline the lookup so there won't be any gain, but with the rather stupid interpreters we've had to deal with for a long time, caching variables in a lower scope could have a significant impact – Christoph Dec 31 '09 at 18:54
  • as for the type conversions: they are done to be consistent with the functions defined by the ECMA-standard; actually, standard functions usually wouldn't check the types, but just do the conversion and plunge ahead; I'll edit to match this style, which should result in a slight performance gain... – Christoph Dec 31 '09 at 18:57
  • @Christoph, caching push and splice is probably ok most of the time but would break any overloading of push and splice. What's you take on this? Also what if the same rotate function is reused on a different class (so to speak), then push and splice would be forced on Array: `Roles.prototype.rotate = Array.prototype.rotate` – Jean Vincent Dec 31 '09 at 20:00
  • @Jean: you have to decide if you want `rotate()` to work with any array-like object (eg `arguments`) by only using generic array functions, or to work with any objects which provides their own implementations of these methods; imo the former is more 'in the spirit' of the standard and more useful as well, as my version of `rotate()` depends on the somewhat specialised `splice()`, ie I don't think there would be a lot of use-cases for the latter; you're right that you should call the overloaded methods when creating your own inheritance hierarchies, though; I'll add some comments to my answer – Christoph Dec 31 '09 at 22:05
  • @Christoph, I agree with you regarding use cases, especially because the original question really is about Arrays and nothing else. However it would still be nice as an added bonus to be able to reuse the function on other objects. Now regarding overloading, does `this.push || push` allow overloading by an intermediate class between this and Array? And, similar question, does `this.push.apply` allow overloading by an intermediate class? Finally, what is the benefit of the alternate version, wouldn't `this.push` always be faster than `this.push || push`? – Jean Vincent Jan 01 '10 at 10:50
  • Ignore my last question, the alternate version helps to use rotate on objects that do not implement `push` or `splice`. The 2 other questions are still valid although I could find the answer myself if I was not that lazy. I also like your answers (that's the drawback of providing good answers, people bother you with questions they could answer themselves). – Jean Vincent Jan 01 '10 at 11:00
  • Regarding the >>> operator, does it really improve performances considering that JS always converts back integers to double? This could result into length being converted to integer, shifted (not), then converted back to double, and finally used as a double later. This could decrease performances unless some JS implementation actually stores (unsigned) integers in variables. In which case one might think that such an implementation would store this.length as an integer and therefore >>> 0 would not improve anything. Also this.length would already be unsigned. – Jean Vincent Jan 01 '10 at 11:48
  • Running some performance tests (FF 3.5 only), I found a very slight performance improvement using `unshift.apply( this, splice.call( this, count % this.length, this.length ) );` over the use of two intermediate variables and the shift operators. I have also tried to put the shift operators directly into the modulo expression and it was slightly slower. – Jean Vincent Jan 01 '10 at 12:08
  • I figured out the answer to method overloading by intermediate classes. The prototype of the object class will have the reference to the last overloaded definition. So in short, it will work. – Jean Vincent Jan 01 '10 at 14:23
  • @Jean: `this.push` is a regular property lookup, ie the prototype chain is followed and intermediate 'classes' will be visited; the shifting (`>>>` and `>>`) is done for type conversion, not performance; if you read the ECMA-spec, you'll see that the built-in functions use things like `ToUint32()` and `ToInt32()`, which are not exposed to the programmer, but can be emulated by unsigned and signed shifts by `0` (a noop for ints); about FF 3.5: with TraceMonkey (Mozillas new JS engine), some of the optmization approaches suited for more primitive interpreters might indeed no longer apply – Christoph Jan 01 '10 at 14:40
  • Running performance tests under FF 3.5, IE8, Safari 4.0, Opera 10.10, with a simplified version of yours (without the count, len and count % len), I found almost no difference between the closure version and the direct version using this.unshift and this.splice. Statistically though, the version without the closure seems a little faster in IE, Safari, but not in FF. There is no clear-cut winner because one cannot control garbage collection and other parasites. So as much as I like the idea of using a closure and caching Array methods, I don't see any real benefit of doing so in this context. – Jean Vincent Jan 01 '10 at 17:19
  • Also, running these tests in Opera 10.10 proved very slow, about 50 times slower than IE8 and 200 times slower than FF3.5. Any idea why? Maybe they don't have native unshift and/or splice? – Jean Vincent Jan 01 '10 at 17:23
  • In http://www.w3schools.com/jsref/jsref_unshift.asp, they mention that `The unshift() method is supported in all major browsers, except Internet Explorer`. It is certainly no longer true in IE8, but would you happen to know which version of IE first supported unshift? – Jean Vincent Jan 01 '10 at 17:46
  • @Jean: `unshift()` was added to IE in version 5.5; I don't know why mutating the arrays is so slow in Opera: either their implementation of either `unshift()` or `splice()` is broken, or there's some fundamental problem with how sparse arrays are represented internally; in most JS engines, sparse arrays will be stored as actual arrays - which seems reasonable, so I don't know why the Opera developers should do this differently; I don't know how you did your benchmarking, but http://pastebin.com/f2793ae87 suggests that caching will increase performance at least in IE8, FF3.5 and Opera10 – Christoph Jan 01 '10 at 17:59
  • ups.. that should read _dense_ arrays, not sparse ;) there's another problem with Opera, though: using the same array in the benchmark code again to time the second function will result in a huge performance hit (ie the second function tested will always be slower); if you use a new array, you'll see that the caching version seems to be faster, but results are inconsistent (too lazy to gather some real statistics ;)); that reusing the array results in a severe performance penalty might suggest that there really is something wrong with Opera's array implementation – Christoph Jan 01 '10 at 18:15
  • My benchmark (http://pastebin.com/f51f54816) is slightly different than yours and shows that between different rounds the time returned varies sometimes to the advantage of one version, sometimes to the advantage of another version. It also shows that some garbage collector or other parasite (windows here) process is messing up with the benchmark. So unless the difference was always in one direction, there is no way to call a winner here. At least not on my machine under FF and other browsers. – Jean Vincent Jan 01 '10 at 19:31
  • This test also clearly shows some kind of leak in Opera 10.10. We should probably report it. – Jean Vincent Jan 01 '10 at 19:34
  • I have reported the bug to Opera. – Jean Vincent Jan 01 '10 at 19:51
  • Note also that the benchmark shows that the non-closure version runs systematically faster on Safari 4.0 for Windows Vista. The difference is about 5%. – Jean Vincent Jan 01 '10 at 19:58
  • Hmm, calling myArr.rotate() with this function doesn't seem to change the order of any elements in the array. Not sure what I'm missing. – Amalgovinus Sep 18 '14 at 22:10
  • 1
    This won't rotate large arrays. I checked today and it could handle only an array of 250891 items in length. Apparently the number of arguments you can pass by `apply` method is limited with a certain call stack size. In ES6 terms the spread operator would also suffer from the same stack size issue. Below i give a map method of doing the same thing. It's slower but works for millions of items. You may also implement map with a simple for or while loop and it will get much faster. – Redu Jun 27 '16 at 10:39
  • @Redu: another possibility would be using `concat` instead of push (but you would lose in-place rotation and genericity), or using `shift`/`push` (or `unshift`/`pop`) in a loop – Christoph Jun 27 '16 at 11:40
  • Don't...ever...modify...objects...you...don't...own!!!!! This is a terrible answer! – Hybrid web dev Jan 15 '20 at 22:05
  • Here is my solution https://stackoverflow.com/a/63606959/569751 – Aamir Afridi Aug 26 '20 at 23:18
  • Can someone please explain this equation: count = ((count % len) + len) % len; Is it not same as : count = count % len; – All2Pie Mar 17 '21 at 08:46
42

I would probably do something like this:

Array.prototype.rotate = function(n) {
    n = n % this.length;
    return this.slice(n, this.length).concat(this.slice(0, n));
}

Edit    Here’s a mutator version:

Array.prototype.rotate = function(n) {
    n = n % this.length;
    while (this.length && n < 0) n += this.length;
    this.push.apply(this, this.splice(0, n));
    return this;
}
Willster
  • 2,436
  • 1
  • 29
  • 32
Gumbo
  • 594,236
  • 102
  • 740
  • 814
  • keep in mind that this function keeps the original array unchanged – Christoph Dec 31 '09 at 13:52
  • It needs to modify the original Array (this), just like push, pop, shift, unshift, concat, and splice do. Other than that this is valid. – Jean Vincent Dec 31 '09 at 18:32
  • @Gumbo, why the while loop? We don't need to make n positive, splice works with negative values just as well. For the end, this is right but pretty much Christoph version which got it right first without the overloading caveat. – Jean Vincent Dec 31 '09 at 20:11
  • @Gumbo, correction on my previous comment, negative numbers work only the splice( n. this.length ) version. Using splice( 0, n ) as in your version, requires a positive int. – Jean Vincent Dec 31 '09 at 20:35
  • 2
    I've added `n = n % this.length` before the return statement to handle negative and/or out of boundary numbers. – iedmrc Jul 20 '20 at 10:49
27

This function works in both way and works with any number (even with number greater than array length):

function arrayRotate(arr, count) {
  count -= arr.length * Math.floor(count / arr.length);
  arr.push.apply(arr, arr.splice(0, count));
  return arr;
}

usage:

for(let i = -6 ; i <= 6 ; i++) {
  console.log(arrayRotate(["","","","",""], i), i);
}

result:

[ "", "", "", "", "" ]    -6
[ "", "", "", "", "" ]    -5
[ "", "", "", "", "" ]    -4
[ "", "", "", "", "" ]    -3
[ "", "", "", "", "" ]    -2
[ "", "", "", "", "" ]    -1
[ "", "", "", "", "" ]    0
[ "", "", "", "", "" ]    1
[ "", "", "", "", "" ]    2
[ "", "", "", "", "" ]    3
[ "", "", "", "", "" ]    4
[ "", "", "", "", "" ]    5
[ "", "", "", "", "" ]    6
Yukulélé
  • 11,464
  • 8
  • 52
  • 76
  • Using this function I ran into an issue because it alters the original array. I found some workarounds here: https://stackoverflow.com/questions/14491405 – ognockocaten Jun 05 '18 at 17:55
  • 1
    @ognockocaten It's not an issue, it's how this function works. if you want to keep original array unaltered, clone it before : `var arr2 = arrayRotate(arr.slice(0), 5)` – Yukulélé Jun 11 '18 at 14:28
  • This is indeed a great answer, and it helped me. However, providing an alternative that doesn't mutate the original array would be best. Sure it's easy to merely create a copy of the original, this isn't best practice as it uses memory unnecessarily. – Hybrid web dev May 27 '19 at 02:52
  • @Hybridwebdev use `const immutatableArrayRotate = (arr, count) => ArrayRotate(arr.clone(), count)` – Yukulélé Apr 18 '20 at 11:03
10

So many of these answers seem over-complicated and difficult to read. I don't think I saw anyone using splice with concat...

function rotateCalendar(){
    var cal=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
    cal=cal.concat(cal.splice(0,new Date().getMonth()));
    console.log(cal);  // return cal;
}

console.log outputs (*generated in May):

["May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr"]

As for compactness, I can offer a couple of generic one-liner functions (not counting the console.log | return portion). Just feed it the array and the target value in the arguments.

I combine these functions into one for a four-player card game program where the array is ['N','E','S','W']. I left them separate in case anyone wants to copy/paste for their needs. For my purposes, I use the functions when seeking whose turn is next to play/act during different phases of the game (Pinochle). I haven't bothered testing for speed, so if someone else wants to, feel free to let me know the results.

*notice, the only difference between functions is the "+1".

function rotateToFirst(arr,val){  // val is Trump Declarer's seat, first to play
    arr=arr.concat(arr.splice(0,arr.indexOf(val)));
    console.log(arr); // return arr;
}
function rotateToLast(arr,val){  // val is Dealer's seat, last to bid
    arr=arr.concat(arr.splice(0,arr.indexOf(val)+1));
    console.log(arr); // return arr;
}

combination function...

function rotateArray(arr,val,pos){
    // set pos to 0 if moving val to first position, or 1 for last position
    arr=arr.concat(arr.splice(0,arr.indexOf(val)+pos));
    return arr;
}
var adjustedArray=rotateArray(['N','E','S','W'],'S',1);

adjustedArray=

W,N,E,S
mickmackusa
  • 33,121
  • 11
  • 58
  • 86
7

Using ES6's spread for an immutable example ...

[...array.slice(1, array.length), array[0]]

and

[array[array.items.length -1], ...array.slice(0, array.length -1)]

It's probably not the most efficient though but it's concise.

Dudley Craig
  • 81
  • 1
  • 4
7

Easy solution with slice and destructuring:

const rotate = (arr, count = 1) => {
  return [...arr.slice(count, arr.length), ...arr.slice(0, count)];
};

const arr = [1,2,3,4,5];

console.log(rotate(arr, 1));  // [2, 3, 4, 5, 1]
console.log(rotate(arr, 2));  // [3, 4, 5, 1, 2]
console.log(rotate(arr, -2)); // [4, 5, 1, 2, 3]
console.log(rotate(arr, -1)); // [5, 1, 2, 3, 4]
kashesandr
  • 1,360
  • 25
  • 31
  • 2
    This is really nice! You could avoid the need for checking for `count === 0` by setting a default value. That would allow you to make this a one-liner: `const rotate = (arr, n = 1) => [...arr.slice(n, arr.length), ...arr.slice(0, n)];` – Nick F Sep 15 '20 at 20:29
4

Here is a very simple way to shift items in an array:

function rotate(array, stepsToShift) {

    for (var i = 0; i < stepsToShift; i++) {
        array.unshift(array.pop());
    }

    return array;
}
aryeh
  • 128
  • 2
  • 6
4

When I couldn't find a ready-made snippet to start a list of days with 'today', I did it like this (not quite generic, probably far less refined than the above examples, but did the job):

//returns 7 day names with today first
function startday() {
    const days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
    let today = new Date();
    let start = today.getDay(); //gets day number
    if (start == 0) { //if Sunday, days are in order
        return days
    }
    else { //if not Sunday, start days with today
        return days.slice(start).concat(days.slice(0,start))
    }
}

Thanks to a little refactor by a better programmer than me it's a line or two shorter than my initial attempt, but any further comments on efficiency are welcome.

Dave Everitt
  • 14,788
  • 6
  • 59
  • 87
3

see http://jsperf.com/js-rotate-array/8

function reverse(a, from, to) {
  --from;
  while (++from < --to) {
    var tmp = a[from];
    a[from] = a[to];
    a[to] = tmp;
  }
}

function rotate(a, from, to, k) {
  var n = to - from;
  k = (k % n + n) % n;
  if (k > 0) {
    reverse(a, from, from + k);
    reverse(a, from + k, to);
    reverse(a, from, to);
  }
}
Vic99999
  • 51
  • 1
  • 2
3
function rotate(arr, k) {
for (var i = 0; i < k+1; i++) {
    arr.push(arr.shift());
}
return arr;
}
//k work as an index array
console.log(rotate([1, 2, 7, 4, 5, 6, 7], 3)); //[5,6,7,1,2,7,4]
console.log(rotate([-1, -100, 3, 99], 2));     //[99,-1,-100,3]
moefarsh
  • 31
  • 1
3
// Example of array to rotate
let arr = ['E', 'l', 'e', 'p', 'h', 'a', 'n', 't'];

// Getting array length
let length = arr.length;

// rotation < 0 (move left), rotation > 0 (move right)
let rotation = 5;

// Slicing array in two parts
let first  = arr.slice(   (length - rotation) % length, length); //['p', 'h', 'a' ,'n', 't']
let second = arr.slice(0, (length - rotation) % length); //['E', 'l', 'e']

// Rotated element
let rotated = [...first, ...second]; // ['p', 'h', 'a' ,'n', 't', 'E', 'l', 'e']

In one line of code:

let rotated = [...arr.slice((length - rotation) % length, length), ...arr.slice(0, (length - rotation) % length)];
Erik Martín Jordán
  • 1,768
  • 1
  • 14
  • 21
3

@Christoph, you've done a clean code, but 60% slowest than this one i found. Look at the result on jsPerf : http://jsperf.com/js-rotate-array/2 [Edit] OK now there is more browsers an that not obvious witch methods the best

var rotateArray = function(a, inc) {
    for (var l = a.length, inc = (Math.abs(inc) >= l && (inc %= l), inc < 0 && (inc += l), inc), i, x; inc; inc = (Math.ceil(l / inc) - 1) * inc - l + (l = inc))
    for (i = l; i > inc; x = a[--i], a[i] = a[i - inc], a[i - inc] = x);
    return a;
};

var array = ['a','b','c','d','e','f','g','h','i'];

console.log(array);
console.log(rotateArray(array.slice(), -1)); // Clone array with slice() to keep original
molokoloco
  • 4,126
  • 2
  • 29
  • 26
  • @molokocolo Intuitively your solution would run slower as the increment would be higher because you're using a loop. I have updated your test case with an increment of 5 and it does appear to then run slower: http://jsperf.com/js-rotate-array/3 – Jean Vincent Oct 23 '11 at 11:42
  • I don't figure what is doing rotateArray() function ^^ only that it's work :) (it's a weird code !) Chrome behave nearly the opposite of Firefox... – molokoloco Oct 23 '11 at 17:00
  • This method is very interesting, it works by swapping element references in the array. The array size never changes which is why it is very fast with the drawback of using loops. The interesting thing that it shows is that Firefox is the fastest at loops while chrome is the fastest at arrays manipulations. – Jean Vincent Oct 24 '11 at 09:37
  • 1
    After analyzing the code, I have found a weakness that shows that this solution is only faster for smaller arrays and certain rotation counts: http://jsperf.com/js-rotate-array/5 Among all browsers the fastest remains Google Chrome with the splice/unshift solution. This means that this is really a matter of Array method optimization that Chrome does better than the other browsers. – Jean Vincent Oct 24 '11 at 18:29
2

The accepted answer has a flaw of not being able to handle arrays larger than the call stack size which depends on the session but should be around like 100~300K items. For instance, in the current Chrome session that i tried it was 250891. In many cases you may not even know to what size the array might dynamically grow into. So that's a serious problem.

To overcome this limitation, I guess one interesting method is utilizing Array.prototype.map() and mapping the elements by rearranging the indices in a circular fashion. This method takes one integer argument. If this argument is positive it will rotate on increasing indices and if negative on decreasing indices direction. This has only O(n) time complexity and will return a new array without mutating the one it's called upon while handling millions of items without any problem. Let see how it works;

Array.prototype.rotate = function(n) {
var len = this.length;
return !(n % len) ? this
                  : n > 0 ? this.map((e,i,a) => a[(i + n) % len])
                          : this.map((e,i,a) => a[(len - (len - i - n) % len) % len]);
};
var a = [1,2,3,4,5,6,7,8,9],
    b = a.rotate(2);
console.log(JSON.stringify(b));
    b = a.rotate(-1);
console.log(JSON.stringify(b));

Actually after i have been criticized on two matters as follows;

  1. There is no need for a conditional for positive or negative input since it reveals a violation of DRY .you could do this with one map because every negative n has a positive equivalent (Totally right..)
  2. An Array function should either change the current array or make a new array, your function could do either depending on whether a shift is necessary or not (Totally right..)

I have decided to modify the code as follows;

Array.prototype.rotate = function(n) {
var len = this.length;
return !(n % len) ? this.slice()
                  : this.map((e,i,a) => a[(i + (len + n % len)) % len]);
};
var a = [1,2,3,4,5,6,7,8,9],
    b = a.rotate(10);
console.log(JSON.stringify(b));
    b = a.rotate(-10);
console.log(JSON.stringify(b));

Then again; of course the JS functors like Array.prototype.map() are slow compared to their equivalents coded in plain JS. In order to gain more than 100% performance boost the following would probably be my choice of Array.prototype.rotate() if i ever need to rotate an array in production code like the one i used in my attempt on String.prototype.diff()

Array.prototype.rotate = function(n){
  var len = this.length,
      res = new Array(this.length);
  if (n % len === 0) return this.slice();
  else for (var i = 0; i < len; i++) res[i] = this[(i + (len + n % len)) % len];
  return res;
};
Redu
  • 19,106
  • 4
  • 44
  • 59
  • shift() and splice(), as slow as they might be are actually faster than using map() or any method using a function parameter. shift() and splice() being more declarative are also easier to optimize in the long term. – Jean Vincent Jun 24 '16 at 17:06
  • @Jean Vincent Yes you are right.. map in fact might turn out to be slower yet i believe the ideal rotating logic is this. I just tried to show the logical approach. Map can easily be simplified down with a for loop and would result significant improvement in speed... However the real thing is, the accepted answer has an approach developed with some other languages in mind. It is not ideal for JS.... won't even run when the array size is larger than the call stack size. (in my case and in this session it turned out to be limited with only 250891 items) So there is that..! – Redu Jun 27 '16 at 10:48
  • You are right about the call stack size that apply can exceed, this is a more valid argument than speed, consider editing your answer to emphasize this point with improved indentation too. – Jean Vincent Jun 27 '16 at 19:27
2

This function is a little faster than the accepted answer for small arrays but MUCH faster for large arrays. This function also allows for an arbitrary number of rotations greater than the length of the array, which is a limitation of the original function.

Lastly, the accepted answer rotates the opposite direction as described.

const rotateForEach = (a, n) => {
    const l = a.length;
    a.slice(0, -n % l).forEach(item => a.push( item ));
    return a.splice(n % l > 0 ? (-n % l) : l + (-n % l));
}

And the functional equivalent (which seems to also have some performance benefits):

const rotateReduce = (arr, n) => {
    const l = arr.length;
    return arr.slice(0, -n % l).reduce((a,b) => {
        a.push( b );
        return a;
    }, arr).splice(n % l> 0 ? l + (-n % l) : -n % l);
};

You can check out the performance breakdown here.

  • Unfortunately it does not work, it shrinks down the array to size zero which explains why it is so much faster, after the first run than any other correct implementation, especially on larger arrays. In your benchmark test if you swap the order of tests, and display a.length at the end, you'll see the problem. – Jean Vincent Nov 07 '16 at 17:17
2

EDIT:: Hey so turns out there's too much iteration happening. No loops, no branching.

Still works with negative n for right rotation and positive n for left rotation for any size n, Mutation free

function rotate(A,n,l=A.length) {
  const offset = (((n % l) + l) %l)
  return A.slice(offset).concat(A.slice(0,offset))
}

Here's the code golf version for giggles

const r = (A,n,l=A.length,i=((n%l)+l)%l)=>A.slice(i).concat(A.slice(0,i))

EDIT1::* Branchless, mutationless implementation.

So hey, turns out I had a branch where I didn't need it. Here is a working solution. negative num = right rotate by |num| positive num = left rotate by num

function r(A,n,l=A.length) {
  return A.map((x,i,a) => A[(((n+i)%l) + l) % l])
}

The equation ((n%l) + l) % l maps exactly positive and negative numbers of any arbitrarily large values of n

ORIGINAL

Rotate left and right. Rotate left with positive n, rotate right with negative n.

Works for obscenely large inputs of n.

No mutation mode. Too much mutation in these answers.

Also, fewer operations than most answers. No pop, no push, no splice, no shift.

const rotate = (A, num ) => {
   return A.map((x,i,a) => {
      const n = num + i
      return n < 0 
        ? A[(((n % A.length) + A.length) % A.length)]
        : n < A.length 
        ? A[n] 
        : A[n % A.length]
   })
}

or

 const rotate = (A, num) => A.map((x,i,a, n = num + i) => 
  n < 0
    ? A[(((n % A.length) + A.length) % A.length)]
    : n < A.length 
    ? A[n] 
    : A[n % A.length])

//test
rotate([...Array(5000).keys()],4101)   //left rotation
rotate([...Array(5000).keys()],-4101000)  //right rotation, num is negative

// will print the first index of the array having been rotated by -i
// demonstrating that the rotation works as intended
[...Array(5000).keys()].forEach((x,i,a) => {
   console.log(rotate(a,-i)[0])
}) 
// prints even numbers twice by rotating the array by i * 2 and getting the first value
//demonstrates the propper mapping of positive number rotation when out of range
[...Array(5000).keys()].forEach((x,i,a) => {
   console.log(rotate(a,i*2)[0])
})

Explanation:

map each index of A to the value at index offset. In this case

offset = num

if the offset < 0 then offset + index + positive length of A will point to the inverse offset.

if offset > 0 and offset < length of A then simply map the current index to the offset index of A.

Otherwise, modulo the offset and the length to map the offset in the bounds of the array.

Take for instance offset = 4 and offset = -4.

When offset = -4, and A = [1,2,3,4,5], for each index, offset + index will make the magnitude (or Math.abs(offset)) smaller.

Let's explain the calculation for the index of negative n first. A[(((n % A.length) + A.length) % A.length)+0] and been intimidated. Don't be. It took me 3 minutes in a Repl to work it out.

  1. We know n is negative because the case is n < 0. If the number is larger than the range of the Array, n % A.length will map it into the range.
  2. n + A.length add that number to A.length to offset n the correct amount.
  3. We know n is negative because the case is n < 0. n + A.length add that number to A.length to offset n the correct amount.
  4. Next Map it to the range of the length of A using modulo. The second modulous is necessary to map the result of the calculation into an indexable range

    enter image description here

  5. First index: -4 + 0 = -4. A.length = 5. A.length - 4 = 1. A2 is 2. Map index 0 to 2. [2,... ]

  6. Next index, -4 + 1 = -3. 5 + -3 = 2. A2 is 3. Map index 1 to 3. [2,3... ]
  7. Etc.

The same process applies to offset = 4. When offset = -4, and A = [1,2,3,4,5], for each index, offset + index will make the magnitude bigger.

  1. 4 + 0 = 0. Map A[0] to the value at A[4]. [5...]
  2. 4 + 1 = 5, 5 is out of bounds when indexing, so map A2 to the value at the remainder of 5 / 5, which is 0. A2 = value at A[0]. [5,1...]
  3. repeat.
nathan rogers
  • 187
  • 5
  • 15
2

Update Feb 2021

A one-liner functions to perform rotate right and rotate left of array elements.

Rotate Left

const arrRotateLeft = (a,n) =>{while (n>0) {a.push(a.shift());n--;}return a;};

Rotate Right

const arrRotateRight= (a,n) =>{while (n>0) {a.unshift(a.pop());n--;}return a;};

const arrRotateLeft = (a,n)=>{while (n>0) {a.push(a.shift());n--;}return a;};

const arrRotateRight= (a,n)=>{while (n>0) {a.unshift(a.pop());n--;}return a;};


//=========== Test rotate Left =================
console.log(arrRotateLeft([1,2,3,4,5,6],0));       // [1,2,3,4,5,6]   <== rotate in this direction
console.log(arrRotateLeft([1,2,3,4,5,6],1));       // [2,3,4,5,6,1]
console.log(arrRotateLeft([1,2,3,4,5,6],2));       // [3,4,5,6,1,2]
console.log(arrRotateLeft([1,2,3,4,5,6],3));       // [4,5,6,1,2,3]
console.log(arrRotateLeft([1,2,3,4,5,6,7,8],5));   // [6,7,8,1,2,3,4,5]


//=========== Test rotate Right =================
console.log(arrRotateRight([1,2,3,4,5,6],0));      // [1,2,3,4,5,6]   ==> rotate in this direction
console.log(arrRotateRight([1,2,3,4,5,6],1));      // [6,1,2,3,4,5]
console.log(arrRotateRight([1,2,3,4,5,6],2));      // [5,6,1,2,3,4]
console.log(arrRotateRight([1,2,3,4,5,6],3));      // [4,5,6,1,2,3]
console.log(arrRotateRight([1,2,3,4,5,6,7,8],5));  // [4,5,6,7,8,1,2,3]
Mohsen Alyafei
  • 2,476
  • 3
  • 13
  • 26
1

How about incrementing a counter and then getting the remainder of a division by the array length to get where you are supposed to be.

var i = 0;
while (true);
{
    var position = i % months.length;
    alert(months[position]);
    ++i;
}

Language syntax aside this should work fine.

tgandrews
  • 11,511
  • 15
  • 41
  • 55
  • 2
    This does not rotate the Array in any way. – Jean Vincent Dec 31 '09 at 18:33
  • 1
    True (as stated in the answer), but it saves having to rotate the array. – tgandrews Jan 01 '10 at 13:08
  • 1
    But the whole point of this IS to rotate an Array, per the title, not to display an array from a certain offset. If you'd use the same loop to rebuild the new array it would terribly slower than other versions using native Array methods. Plus you forgot to break out of the infinite loop. – Jean Vincent Jan 01 '10 at 17:16
1
Follow a simpler approach of running a loop to n numbers and shifting places upto that element.

function arrayRotateOne(arr, n) {
  for (let i = 0; i < n; i++) {
    arr.unshift(arr.pop());
  }
  return arr;
}
console.log( arrayRotateOne([1,2,3,4,5,6],2));



function arrayRotateOne(arr,n) {
  for(let i=0; i<n;i++){
      arr.push(arr.shift());
      console.log('execute',arr)
    }
     return arr;
 }

console.log( arrayRotateOne([1,2,3,4,5,6],2));

1

Non mutating solution

var arr = ['a','b','c','d']
arr.slice(1,arr.length).concat(arr.slice(0,1)

with mutation

var arr = ['a','b','c','d']
arr = arr.concat(arr.splice(0,1))
Haseeb A
  • 2,640
  • 23
  • 26
1

I am sharing my solution which I am using for rotating on carousel. It might break when array size is smaller than displayCount, but you could add extra condition to stop rotating when it's small, or concatenating the main array *displayCount times too.

function rotate(arr, moveCount, displayCount) {
  const size = arr.length;

  // making sure startIndex is between `-size` and `size`
  let startIndex = moveCount % size;
  if (startIndex < 0) startIndex += size; 

  return [...arr, ...arr].slice(startIndex, startIndex + displayCount);
}

// move 3 to the right and display 4 items
// rotate([1,2,3,4,5], 3, 4) -> [4,5,1,2]

// move 3 to the left and display 4 items
// rotate([1,2,3,4,5], -3, 4) -> [3,4,5,1]

// move 11 to the right and display 4
// rotate([1,2,3,4,5], 3, 4) -> [2,3,4,5]
emil
  • 4,978
  • 3
  • 23
  • 34
0

@molokoloco I needed a function that I could configure to rotate in a direction - true for forward and false for backward. I created a snippet that takes a direction, a counter and an array and outputs an object with the counter incremented in the appropriate direction as well as prior, current, and next values. It does NOT modify the original array.

I also clocked it against your snippet and although it is not faster, it is faster than the ones you compare yours with - 21% slower http://jsperf.com/js-rotate-array/7 .

function directionalRotate(direction, counter, arr) {
  counter = direction ? (counter < arr.length - 1 ? counter + 1 : 0) : (counter > 0 ? counter - 1 : arr.length - 1)
  var currentItem = arr[counter]
  var priorItem = arr[counter - 1] ? arr[counter - 1] : arr[arr.length - 1]
  var nextItem = arr[counter + 1] ? arr[counter + 1] : arr[0]
  return {
    "counter": counter,
    "current": currentItem,
    "prior": priorItem,
    "next": nextItem
  }
}
var direction = true // forward
var counter = 0
var arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'];

directionalRotate(direction, counter, arr)
saranicole
  • 903
  • 1
  • 12
  • 18
  • This does not rotate the Array. To answer this question, you need to return an Array where the first item it at counter. – Jean Vincent Nov 20 '12 at 20:58
0

If your array is going to be large and/or you are going to rotate a lot, you might want to consider using a linked list instead of an array.

Thomas Eding
  • 31,027
  • 10
  • 64
  • 101
0

I am coming late but I have a brick to add to these good answers. I was asked to code such a function and I first did:

Array.prototype.rotate = function(n)
{
    for (var i = 0; i < n; i++)
    {
        this.push(this.shift());
    }
    return this;
}

But it appeared to be less efficient than following when n is big:

Array.prototype.rotate = function(n)
{
    var l = this.length;// Caching array length before map loop.

    return this.map(function(num, index) {
        return this[(index + n) % l]
    });
}
antoni
  • 3,652
  • 25
  • 37
0

I am not sure if this is the most efficient way but I like the way it reads, it's fast enough for most large tasks as I have tested it on production...

function shiftRight(array) {
  return array.map((_element, index) => {
    if (index === 0) {
      return array[array.length - 1]
    } else return array[index - 1]
  })
}

function test() {
  var input = [{
    name: ''
  }, 10, 'left-side'];
  var expected = ['left-side', {
    name: ''
  }, 10]
  var actual = shiftRight(input)

  console.log(expected)
  console.log(actual)

}

test()
0

Native, fast, small, semantic, works on old engines and "curryable".

function rotateArray(offset, array) {
    offset = -(offset % array.length) | 0 // ensure int
    return array.slice(offset).concat(
        array.slice(0, offset)
    )
}
Leo Dutra
  • 112
  • 2
  • 11
0

** Using Latest version of JS we can build it every easily **

 Array.prototype.rotateLeft = function (n) {
   this.unshift(...this.splice(-(n), n));
    return this
  }

here moves: number of rotations ,a Array that you can pass random number

let a = [1, 2, 3, 4, 5, 6, 7];
let moves = 4;
let output = a.rotateLeft(moves);
console.log("Result:", output)
0

Array in JS has below built in method which can be used to rotate an array quite easily and obviously these methods are immutable in nature.

  • push: Inserts the item to end of the array.
  • pop: Removes the item from the end of the array.
  • unshift: Inserts the item to the beginning of the array.
  • shift: Removes the item from the beginning of the array.

The below solution (ES6) takes two arguments , array needs to be rotated and n , number of times the array should be rotated.

const rotateArray = (arr, n) => {
  while(arr.length && n--) {
    arr.unshift(arr.pop());
  }
  return arr;
}

rotateArray(['stack', 'overflow', 'is', 'Awesome'], 2) 
// ["is", "Awesome", "stack", "overflow"]

It can be added to Array.prototype and can be used all across your application

Array.prototype.rotate = function(n) {
 while(this.length && n--) {
   this.unshift(this.pop());
 }
 return this;
}
[1,2,3,4].rotate(3); //[2, 3, 4, 1]
Shushanth Pallegar
  • 2,654
  • 1
  • 12
  • 15
0

Using for loop. Here are the steps

  1. Store first element of array as temp variable.
  2. Then swap from left to right.
  3. Then assign temp variable to last element of array.
  4. Repeat these steps for number of rotations.

function rotateLeft(arr, rotations) {
    let len = arr.length;
    for(let i=0; i<rotations; i++){ 
        let temp = arr[0];
        for(let i=0; i< len; i++){
            arr[i]=arr[i+1];
        }
        arr[len-1]=temp;
    }
    return arr;
}

let arr = [1,2,3,4,5];

let rotations = 3;
let output = rotateLeft(arr, rotations);
console.log("Result Array => ", output);
Maqsood Ahmed
  • 391
  • 2
  • 7
0

with es6 syntax

function rotLeft(a, d) {
    const removed = a.splice(0,d);
    return [...a, ...removed];
}
Sujoy Saha
  • 110
  • 7
0

Use the following -

arr=[1,2,3,4,5]  
let arrs=[]
arrs=arr.slice(d%arr.length).concat(arr.slice(0,d%arr.length))

Where, d is no.of rotations
Best Solution, no need of applying Brute force techniques of Popping Pushing with O(1) time complexity

Tushar Walzade
  • 3,182
  • 4
  • 26
  • 45
0
function solution (months, k) {
    var u = [1];
    for (let i = 0; i < k; i++) {
        months.splice(0, 0, months[months.length - 1]);
        months.splice(months.length-1, 1);
    }
    return months;
}
Ade
  • 343
  • 2
  • 13
0

I did it like this without any helper libraries

function shiftArrayValuesRight(A, K) {
  // where A is the array, K is the number of times to shift
  let b = []
  if(A.length > 0) {
    for(let i = 0; i < K; i++){
      b[0] = A[A.length - 1]
      for(let j = 1; j < A.length; j++) {
        b[j] = A[j-1]
      }
      A = b
      b = []
    }
    return A 
  } else{
      return A
  }
}
Y-B Cause
  • 1,101
  • 12
  • 35
0
let array = [3, 9, 2, 4, 5, 7, 8];

const shiftLeft = (arr = [], n) => {
    for (let index = 0; index < n; index++) {
       arr.push(arr.shift())
    }
 return arr
}

shiftLeft(array, 2)

-2

Not sure about the efficiency but I would do it in this non mutating way :

 Array.prototype.rotate = function( n ) {
  
   return this.map( (item, index)=> this[ (this.length + index + n)%this.length ] )
 }
Mortec
  • 1
  • So, the first part of your answer "Not sure about efficiency" is like saying "I don't know if this answers your question at all". The original question does not ask "what is a good way to rotate an array", they specifically ask about compactness and speed (memory and time efficiency respectively). The question is all about efficiency and you have not answered that component of it. – richardpringle Jan 31 '20 at 17:00