JavaScript Array rotate()
我想知道旋转一个javascript数组最有效的方法是什么。
我提出了这个解决方案,其中一个正的
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框架?(仍然没有人回答)
您可以使用
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']; |
如果您需要
类型安全,使数组发生变化的通用版本:
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提出了这样一个问题,即代码不支持
1 | push.apply(this, splice.call(this, 0, count)); |
有了这个:
1 | (this.push || push).apply(this, (this.splice || splice).call(this, 0, count)); |
在歌剧10中使用
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。在许多情况下,您甚至可能不知道数组可能动态增长到什么大小。所以这是个严重的问题。
为了克服这个限制,我想一个有趣的方法是利用
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)); |
事实上,在我受到以下两个方面的批评之后;
我决定修改代码如下:
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)); |
当然,像
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; } |
但是,当
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]) } |
方程
原件
左右旋转。用正
为
无突变模式。这些答案有太多的突变。
而且,比大多数答案更少的操作。不弹出,不推,不拼接,不移位。
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 |
如果
如果
否则,对偏移量和长度进行模运算,以映射数组边界中的偏移量。
以
当
我们先解释一下负n指数的计算。以东十一〔十三〕被恐吓。不要这样。我花了3分钟的时间做了一个答复。
接下来将其映射到使用模的长度范围。第二个模是将计算结果映射到可索引范围所必需的。
第一个索引:-4+0=-4。长度=5。a.长度-4=1。A2为2。将索引0映射到2。
同样的过程也适用于
@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; } |
撇开语言语法不谈,这应该可以很好地工作。