JavaScript 配列を回転させる最も効率的な方法は何だろうと思っていました。
正のn
は配列を右に回転し、負のn
は左に回転する(-length < n < length
):
Array.prototype.rotateRight = function( n ) {
this.unshift( this.splice( n, this.length ) )
}
これはこの方法で使用できます:
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
months.rotate( new Date().getMonth() )
上記の元のバージョンには、以下のコメントの Christoph で指摘されているように、欠陥があります。正しいバージョンは次のとおりです(追加のリターンにより連鎖が許可されます)。
Array.prototype.rotateRight = function( n ) {
this.unshift.apply( this, this.splice( n, this.length ) )
return this;
}
おそらくJavaScriptフレームワークのコンテキストで、よりコンパクトで高速なソリューションがありますか? (以下の提案されたバージョンはいずれも、よりコンパクトでも高速でもありません)
配列回転が組み込まれたJavaScriptフレームワークはありますか? (まだ誰も答えていない)
配列を変更するタイプセーフな汎用バージョン:
_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()
のオーバーロードをサポートしていないという問題を提起しました。私はこれは本当に便利だとは思わないが(コメントを参照)、簡単な解決策(ただしハックのようなもの)は行を置き換えることだろう
_Push.apply(this, splice.call(this, 0, count));
_
これで:
_(this.Push || Push).apply(this, (this.splice || splice).call(this, 0, count));
_
unshift()
の代わりにPush()
を使用すると、Opera 10ではほぼ2倍速くなりますが、FFの違いは無視できます。コードは:
_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;
};
})();
_
Push()
、pop()
、shift()
およびunshift()
メソッドを使用できます。
function arrayRotate(arr, reverse) {
if (reverse) arr.unshift(arr.pop());
else arr.Push(arr.shift());
return arr;
}
使用法:
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
私はおそらく次のようなことをします:
Array.prototype.rotate = function(n) {
return this.slice(n, this.length).concat(this.slice(0, n));
}
編集ミューテーターのバージョンは次のとおりです。
Array.prototype.rotate = function(n) {
while (this.length && n < 0) n += this.length;
this.Push.apply(this, this.splice(0, n));
return this;
}
この関数は両方の方法で機能し、任意の数(配列の長さよりも大きい数でも)で機能します。
function arrayRotate(arr, count) {
count -= arr.length * Math.floor(count / arr.length)
arr.Push.apply(arr, arr.splice(0, count))
return arr
}
例:
function stringRotate(str, count) {
return arrayRotate(str.split(''), count).join('')
}
for(let i = -6 ; i <= 6 ; i++) {
console.log(stringRotate("Hello", i), i)
}
結果:
"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
これらの答えの多くは、複雑すぎて読みにくいようです。私は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の出力(* 5月に生成):
["May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr"]
コンパクトさについては、いくつかの汎用ワンライナー関数を提供できます(console.log | return部分はカウントしません)。引数に配列とターゲット値を渡すだけです。
これらの機能を、配列が['N'、 'E'、 'S'、 'W']である4人用のカードゲームプログラム用に1つに組み合わせます。誰かが必要に応じてコピー/貼り付けをしたい場合に備えて、それらを個別に残しました。私の目的のために、ゲームのさまざまな段階でプレイ/アクションの次のターンを探すときに関数を使用します(Pinochle)。スピードをテストする手間がかかっていないので、誰か他の人が望むなら、気軽に結果を知らせてください。
*注意、機能間の唯一の違いは「+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;
}
組み合わせ機能...
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);
adjustArray =
W,N,E,S
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);
}
}
@Christoph、あなたはきれいなコードをやりましたが、私が見つけたものよりも60%遅い jsPerfの結果を見てください。 http://jsperf.com/js-rotate-array/2 [編集] OK
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
「今日」で日のリストを開始するための既製のスニペットを見つけることができなかったとき、私はこのようにしました(非常に一般的ではなく、おそらく上記の例よりもはるかに洗練されていませんが、仕事をしました):
//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行短くなりましたが、効率についてのコメントは歓迎します。
受け入れられた答えには、セッションに依存しますが、約100〜30万アイテムの呼び出しスタックサイズより大きい配列を処理できないという欠陥があります。たとえば、現在のChromeセッションでは250891でした。多くの場合、配列が動的に成長するサイズがわからない場合があります。それは深刻な問題です。
この制限を克服するための興味深い方法の1つは、Array.prototype.map()
を利用し、インデックスを円形に再配置して要素をマッピングすることです。このメソッドは、1つの整数引数を取ります。この引数が正の場合、インデックスが増加すると回転し、インデックスが減少すると負になります。これはO(n)時間の複雑さのみを持ち、何百万ものアイテムを問題なく処理しながら、要求されたものを変更せずに新しい配列を返します。
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));
実際、次の2つの問題について批判された後、
コードを次のように変更することにしました。
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でコーディングされた同等の機能と比較して低速です。 Array.prototype.rotate()
での試行で使用したような実動コードで配列を回転させる必要がある場合、100%以上のパフォーマンスブーストを得るために、おそらく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;
};
この関数は、小さな配列では受け入れられる答えよりも少し高速ですが、大きな配列でははるかに高速です。この関数は、元の関数の制限である配列の長さよりも大きい任意の回転数も許可します。
最後に、受け入れられた答えは、説明されているように反対方向に回転します。
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));
}
そして、機能的に同等のもの(これにはパフォーマンス上の利点もあるようです):
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);
};
パフォーマンスの詳細はこちら をご覧ください
配列内の項目をシフトする非常に簡単な方法を次に示します。
function rotate(array, stepsToShift) {
for (var i = 0; i < stepsToShift; i++) {
array.unshift(array.pop());
}
return array;
}
不変の例のためにES6のスプレッドを使用する...
[...array.slice(1, array.length), array[0]]
そして
[array[array.items.length -1], ...array.slice(0, array.length -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));
var arr = ['a','b','c','d']
arr.slice(1,arr.length).concat(arr.slice(0,1)
var arr = ['a','b','c','d']
arr = arr.concat(arr.splice(0,1))
@molokolocoある方向に回転するように設定できる関数が必要でした-前方の場合はtrue、後方の場合はfalseです。方向、カウンター、および配列を取り、適切な方向だけでなく、前の値、現在の値、および次の値でインクリメントされたカウンターを持つオブジェクトを出力するスニペットを作成しました。元の配列は変更しません。
私もあなたのスニペットに対してそれを記録しました、そしてそれはより速くありませんが、あなたがあなたのものと比較するものより速いです-21%より遅い 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)
EDIT ::繰り返しが多すぎることがわかりました。ループも分岐もありません。
任意のサイズnで右回転の場合は負のn、左回転の場合は正のnで引き続き動作し、突然変異はありません
_function rotate(A,n,l=A.length) {
const offset = (((n % l) + l) %l)
return A.slice(offset).concat(A.slice(0,offset))
}
_
ここに笑いのためのコードゴルフバージョンがあります
_const r = (A,n,l=A.length,i=((n%l)+l)%l)=>A.slice(i).concat(A.slice(0,i))
_
EDIT1 :: *ブランチレス、ミューテーションレス実装。
だから、私はそれを必要としないブランチを持っていたことがわかります。これが実用的なソリューションです。負のnum = | num |だけ右回転正のnum =左にnum回転
_function r(A,n,l=A.length) {
return A.map((x,i,a) => A[(((n+i)%l) + l) % l])
}
_
方程式_((n%l) + l) % l
_は、nの任意の大きな値の正と負の数を正確にマップします
[〜#〜] original [〜#〜]
左右に回転します。正のn
で左に回転し、負のn
で右に回転します。
n
のわいせつに大きい入力に対して機能します。
突然変異モードなし。これらの回答の突然変異が多すぎます。
また、ほとんどの回答よりも少ない操作。ポップ、プッシュ、スプライス、シフトはありません。
_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]
})
}
_
または
_ 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の各インデックスをインデックスオフセットの値にマップします。この場合
_offset = num
_
_offset < 0
_の場合、_offset + index + positive length of A
_は逆オフセットを指します。
_offset > 0 and offset < length of A
_の場合、単に現在のインデックスをAのオフセットインデックスにマップします。
それ以外の場合は、オフセットと長さをモジュロして、配列の境界にオフセットをマッピングします。
インスタンス_offset = 4
_と_offset = -4
_を例にとります。
_offset = -4
_、および_A = [1,2,3,4,5]
_の場合、各インデックスについて、_offset + index
_は大きさ(またはMath.abs(offset)
)を小さくします。
まず、負のnのインデックスの計算について説明しましょう。 A[(((n % A.length) + A.length) % A.length)+0]
と脅されました。 しないでください。Replで解決するのに3分かかりました。
n < 0
_であるため、n
が負であることを知っています。数値が配列の範囲よりも大きい場合、_n % A.length
_は範囲にマップします。n + A.length
_をその数値を_A.length
_に追加して、nを正しい量だけオフセットします。n < 0
_であるため、n
が負であることを知っています。 _n + A.length
_をその数値を_A.length
_に追加して、nを正しい量だけオフセットします。次に、モジュロを使用してAの長さの範囲にマッピングします。 2番目のモジュールは、計算結果をインデックス可能な範囲にマッピングするために必要です。
最初のインデックス:-4 + 0 = -4。 A.length =5。A.length-4 = 1. A 2 は2です。インデックス0から2にマップします。_[2,... ]
_
[2,3... ]
_同じプロセスが_offset = 4
_に適用されます。 _offset = -4
_、および_A = [1,2,3,4,5]
_の場合、各インデックスに対して、_offset + index
_は大きさを大きくします。
カルーセルの回転に使用しているソリューションを共有しています。配列サイズがdisplayCount
よりも小さい場合は破損する可能性がありますが、小さい場合に回転を停止するための追加条件を追加したり、メイン配列を* displayCount回連結したりできます。
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]
これが最も効率的な方法であるかどうかはわかりませんが、読み方が好きです。実稼働環境でテストしているので、ほとんどの大規模なタスクに十分な速さです...
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()
私は遅れていますが、これらの良い答えに追加するレンガがあります。私はそのような関数をコーディングするように頼まれ、最初にやった:
Array.prototype.rotate = function(n)
{
for (var i = 0; i < n; i++)
{
this.Push(this.shift());
}
return this;
}
ただし、n
が大きい場合は、以下のように効率が低下するように見えました。
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]
});
}
カウンターをインクリメントしてから、配列の長さで除算の残りを取得して、現在の位置を取得するのはどうですか。
var i = 0;
while (true);
{
var position = i % months.length;
alert(months[position]);
++i;
}
これ以外の言語構文は問題なく動作するはずです。
配列が大きくなる場合、および/または大量に回転する場合は、配列の代わりにリンクリストを使用することを検討してください。