Javascript数组旋转 rotate()

JavaScript Array rotate()

我想知道旋转一个javascript数组最有效的方法是什么。

我提出了这个解决方案,其中一个正的n将数组向右旋转,一个负的n向左旋转(-length < n < length):

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

然后可以这样使用:

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

正如克里斯托夫在下面的评论中指出的,我上面的原始版本有一个缺陷,正确的版本是(附加的返回允许链接):

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

是否有更紧凑和/或更快的解决方案,可能是在JavaScript框架的上下文中?(以下建议的版本都不是更紧凑或更快)

是否有内置数组旋转的javascript框架?(仍然没有人回答)


您可以使用push()pop()shift()unshift()方法:

1
2
3
4
5
function arrayRotateOne(arr, reverse) {
  if (reverse) arr.unshift(arr.pop());
  else arr.push(arr.shift());
  return arr;
}

用途:

1
2
arrayRotate(['h','e','l','l','o']);       // ['e','l','l','o','h'];
arrayRotate(['h','e','l','l','o'], true); // ['o','h','e','l','l'];

如果您需要count参数,请参阅我的其他答案:https://stackoverflow.com/a/33451102


类型安全,使数组发生变化的通用版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
    };
})();

在评论中,Jean提出了这样一个问题,即代码不支持push()splice()的重载。我不认为这是真正有用的(见评论),但一个快速的解决方案(有点像黑客)是替换行

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

有了这个:

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

在歌剧10中使用unshift()而不是push()的速度几乎是使用unshift()的两倍,而ff的差异可以忽略不计;代码:

1
2
3
4
5
6
7
8
9
10
11
12
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;
    };
})();


我可能会这样做:

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

编辑下面是一个变异版本:

1
2
3
4
5
Array.prototype.rotate = function(n) {
    while (this.length && n < 0) n += this.length;
    this.push.apply(this, this.splice(0, n));
    return this;
}


此函数以两种方式工作,并与任何数字(即使数字大于数组长度)一起工作:

1
2
3
4
5
function arrayRotate(arr, count) {
  count -= arr.length * Math.floor(count / arr.length)
  arr.push.apply(arr, arr.splice(0, count))
  return arr
}

例子:

1
2
3
4
5
6
function stringRotate(str, count) {
  return arrayRotate(str.split(''), count).join('')
}
for(let i = -6 ; i <= 6 ; i++) {
  console.log(stringRotate("Hello", i), i)
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
"oHell", -6
"Hello", -5
"elloH", -4
"lloHe", -3
"loHel", -2
"oHell", -1
"Hello",  0
"elloH",  1
"lloHe",  2
"loHel",  3
"oHell",  4
"Hello",  5
"elloH",  6


这些答案中的许多似乎过于复杂,难以阅读。我想我没看到有人用混凝土拼接…

1
2
3
4
5
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输出(*5月生成):

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

至于紧凑性,我可以提供一些通用的一行函数(不包括console.log返回部分)。只需将数组和目标值输入参数即可。

我将这些功能组合成一个四人牌游戏程序,其中数组是['N'、'E'、'S'、'W']。我把它们分开,以防有人为了他们的需要而复制/粘贴。出于我的目的,在游戏的不同阶段(皮诺克尔),我在寻找下一个轮到谁的角色时使用这些功能。我没费心测试速度,所以如果有人愿意,请随时告诉我测试结果。

*注意,函数之间的唯一区别是"+1"。

1
2
3
4
5
6
7
8
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;
}

组合函数…

1
2
3
4
5
6
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);

可调节半径

1
W,N,E,S

参见http://jspef.com/js-rotate-array/8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
  }
}


@克里斯托夫,你做了一个干净的代码,但比我发现的这个慢了60%。查看jspef上的结果:http://jspef.com/js-rotate-array/2[编辑]好了,现在有更多的浏览器和不明显的切换方法是最好的

1
2
3
4
5
6
7
8
9
10
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


这个函数比小数组的公认答案快一点,但对于大数组则快得多。此函数还允许大于数组长度的任意旋转次数,这是原始函数的限制。

最后,被接受的答案按照描述的相反方向旋转。

1
2
3
4
5
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));
}

以及功能等效物(似乎也有一些性能优势):

1
2
3
4
5
6
7
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);
};

您可以在这里查看性能分析。


以下是一种非常简单的方法来移动数组中的项:

1
2
3
4
5
6
7
8
function rotate(array, stepsToShift) {

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

    return array;
}

接受的答案有一个缺陷,即不能处理大于调用堆栈大小的数组,这取决于会话,但应该是大约100~300k项。例如,在我尝试的当前chrome会话中,它是250891。在许多情况下,您甚至可能不知道数组可能动态增长到什么大小。所以这是个严重的问题。

为了克服这个限制,我想一个有趣的方法是利用Array.prototype.map()并通过循环重新排列索引来映射元素。此方法接受一个整数参数。如果这个参数为正,它将随着指数的增加而旋转,如果为负,则随着指数的减少而旋转。这只具有O(N)时间复杂性,并将返回一个新的数组,而不会改变它在处理数百万个项目时调用的数组,而不会出现任何问题。让我们看看它是如何工作的;

1
2
3
4
5
6
7
8
9
10
11
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));

事实上,在我受到以下两个方面的批评之后;

  • 不需要有条件的正或负输入,因为它显示了对dry的违反。您可以用一个map来实现这一点,因为每个负n都有一个正等价物(完全正确)。
  • 数组函数应该更改当前数组或创建新数组,您的函数可以根据是否需要移位来执行(完全正确)。
  • 我决定修改代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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));

    当然,像Array.prototype.map()这样的JS函数与用普通JS编码的等效函数相比,速度较慢。为了获得100%以上的性能提升,如果我需要在生产代码中旋转一个数组,就像我在尝试String.prototype.diff()时使用的数组,那么下面可能是我选择的Array.prototype.rotate()

    1
    2
    3
    4
    5
    6
    7
    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;
    };


    当我找不到现成的代码片段来用"今天"开始一个日期列表时,我就这样做了(不是很普通,可能比上面的例子要精细得多,但做了工作):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //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))
        }
    }

    多亏了一个比我更好的程序员的一点重构,它比我最初的尝试短了一两行,但欢迎对效率作进一步的评论。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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));


    使用ES6的排列作为不变的例子…

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

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

    它可能不是最有效的,但它很简洁。


    我来晚了,但我有一块砖要加上这些好答案。我被要求编写这样一个函数的代码,我首先做到了:

    1
    2
    3
    4
    5
    6
    7
    8
    Array.prototype.rotate = function(n)
    {
        for (var i = 0; i < n; i++)
        {
            this.push(this.shift());
        }
        return this;
    }

    但是,当n很大时,它的效率似乎不如以下所述:

    1
    2
    3
    4
    5
    6
    7
    8
    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]
        });
    }

    编辑:嘿,原来迭代太多了。没有回路,没有分支。

    仍然适用于右旋转为负N,任何尺寸N的左旋转为正N,无突变

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

    这是格格笑的高尔夫编码版本

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

    Eddi1::*无分支、无突变的实现。

    嘿,原来我有一个不需要的树枝。这是一个有效的解决方案。负数值=右旋转数值|正数值=左旋转数值

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

    方程((n%l) + l) % l精确地映射任意大的n值的正数和负数。

    原件

    左右旋转。用正n向左旋转,用负n向右旋转。

    n的大量输入工作。

    无突变模式。这些答案有太多的突变。

    而且,比大多数答案更少的操作。不弹出,不推,不拼接,不移位。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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]
       })
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     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])
    })

    说明:

    将a的每个索引映射到索引偏移处的值。在这种情况下

    1
    offset = num

    如果offset < 0,那么offset + index + positive length of A将指向反向偏移。

    如果offset > 0 and offset < length of A,那么只需将当前索引映射到a的偏移索引。

    否则,对偏移量和长度进行模运算,以映射数组边界中的偏移量。

    offset = 4offset = -4为例。

    offset = -4A = [1,2,3,4,5]对每个指数而言,offset + index将使幅度(或Math.abs(offset)变小。

    我们先解释一下负n指数的计算。以东十一〔十三〕被恐吓。不要这样。我花了3分钟的时间做了一个答复。

  • 我们知道n是阴性的,因为情况是n < 0。如果数字大于数组的范围,n % A.length将把它映射到范围中。
  • n + A.length将该数字加在A.length上,以抵消正确的n。数量。
  • 我们知道n是阴性的,因为情况是n < 0n + A.length将该数字加到A.length中,以抵消正确的数字n。
  • 接下来将其映射到使用模的长度范围。第二个模是将计算结果映射到可索引范围所必需的。

    enter image description here

  • 第一个索引:-4+0=-4。长度=5。a.长度-4=1。A2为2。将索引0映射到2。[2,... ]

  • 下一个索引,-4+1=-3。5±3=2。A2为3。将索引1映射到3。[2,3... ]
  • 等。
  • 同样的过程也适用于offset = 4。当offset = -4A = [1,2,3,4,5]对每个指数而言,offset + index将使幅度变大。

  • 江户十一〔29〕号。将[0]映射到[4]处的值。[5...]
  • 4 + 1 = 5,索引时5超出界限,因此将a2映射到5 / 5的剩余值,为0。A2=ATA〔0〕。[5,1...]
  • 重复。

  • @molokoloco我需要一个我可以配置为朝着一个方向旋转的函数-向前为真,向后为假。我创建了一个代码片段,它接受一个方向、一个计数器和一个数组,并输出一个对象,该对象的计数器在适当的方向以及之前、当前和下一个值上递增。它不会修改原始数组。

    我还将它与你的代码片段进行了对比,虽然它并不快,但它比你的代码片段要快,比你的代码片段慢21%,比http://jspef.com/js-rotate-array/7慢。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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)


    如果您的数组很大并且/或者要旋转很多,您可能需要考虑使用链接列表而不是数组。


    我不确定这是否是最有效的方法,但我喜欢它的阅读方式,它的速度足以满足大多数大型任务,因为我已经在生产中测试了它…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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()


    如何增加一个计数器,然后通过数组长度得到一个除法的剩余部分,以得到您应该在的位置。

    1
    2
    3
    4
    5
    6
    7
    var i = 0;
    while (true);
    {
        var position = i % months.length;
        alert(months[position]);
        ++i;
    }

    撇开语言语法不谈,这应该可以很好地工作。