web-dev-qa-db-ja.com

JavaScript配列rotate()

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フレームワークはありますか? (まだ誰も答えていない)

62
Jean Vincent

配列を変更するタイプセーフな汎用バージョン:

_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;
    };
})();
_
48
Christoph

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

115
Yukulélé

私はおそらく次のようなことをします:

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;
}
32
Gumbo

この関数は両方の方法で機能し、任意の数(配列の長さよりも大きい数でも)で機能します。

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
19
Yukulélé

これらの答えの多くは、複雑すぎて読みにくいようです。私は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
5
mickmackusa

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);
  }
}
3
Vic99999

@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
3
molokoloco

「今日」で日のリストを開始するための既製のスニペットを見つけることができなかったとき、私はこのようにしました(非常に一般的ではなく、おそらく上記の例よりもはるかに洗練されていませんが、仕事をしました):

//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行短くなりましたが、効率についてのコメントは歓迎します。

2
Dave Everitt

受け入れられた答えには、セッションに依存しますが、約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つの問題について批判された後、

  1. DRYの違反を明らかにするので、正または負の入力の条件は必要ありません。すべての負のnには正の等価(完全に正しい..)
  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;
};
2
Redu

この関数は、小さな配列では受け入れられる答えよりも少し高速ですが、大きな配列でははるかに高速です。この関数は、元の関数の制限である配列の長さよりも大きい任意の回転数も許可します。

最後に、受け入れられた答えは、説明されているように反対方向に回転します。

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

パフォーマンスの詳細はこちら をご覧ください

2
Tanner Stults

配列内の項目をシフトする非常に簡単な方法を次に示します。

function rotate(array, stepsToShift) {

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

    return array;
}
2
Aryeh Harris

不変の例のためにES6のスプレッドを使用する...

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

そして

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

おそらく最も効率的ではありませんが、簡潔です。

1
Dudley Craig
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
Bhaskar Mishra

非変異ソリューション

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))
0
Haseeb Eqx

@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)
0
saranicole

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分かかりました。

  1. ケースが_n < 0_であるため、nが負であることを知っています。数値が配列の範囲よりも大きい場合、_n % A.length_は範囲にマップします。
  2. _n + A.length_をその数値を_A.length_に追加して、nを正しい量だけオフセットします。
  3. ケースが_n < 0_であるため、nが負であることを知っています。 _n + A.length_をその数値を_A.length_に追加して、nを正しい量だけオフセットします。
  4. 次に、モジュロを使用してAの長さの範囲にマッピングします。 2番目のモジュールは、計算結果をインデックス可能な範囲にマッピングするために必要です。

    enter image description here

  5. 最初のインデックス:-4 + 0 = -4。 A.length =5。A.length-4 = 1. A 2 は2です。インデックス0から2にマップします。_[2,... ]_

  6. 次のインデックス、-4 + 1 = -3。 5 + -3 = 2. A 2 は3です。インデックス1を3にマップします。_[2,3... ]_
  7. 等。

同じプロセスが_offset = 4_に適用されます。 _offset = -4_、および_A = [1,2,3,4,5]_の場合、各インデックスに対して、_offset + index_は大きさを大きくします。

  1. _4 + 0 = 0_。 A [0]をA [4]の値にマッピングします。 _[5...]_
  2. _4 + 1 = 5_、5はインデックス作成時に範囲外であるため、A 2 を_5 / 5_の残りの値(0)にマッピングします。A 2 = = A [0]の値。 _[5,1...]_
  3. 繰り返す。
0
nathan rogers

カルーセルの回転に使用しているソリューションを共有しています。配列サイズが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]
0
emil

これが最も効率的な方法であるかどうかはわかりませんが、読み方が好きです。実稼働環境でテストしているので、ほとんどの大規模なタスクに十分な速さです...

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
Andy Gonzalez

私は遅れていますが、これらの良い答えに追加するレンガがあります。私はそのような関数をコーディングするように頼まれ、最初にやった:

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

カウンターをインクリメントしてから、配列の長さで除算の残りを取得して、現在の位置を取得するのはどうですか。

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

これ以外の言語構文は問題なく動作するはずです。

0
tgandrews

配列が大きくなる場合、および/または大量に回転する場合は、配列の代わりにリンクリストを使用することを検討してください。

0
Thomas Eding