В JavaScript повернуть массив ()

мне было интересно, какой самый эффективный способ, чтобы повернуть на 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() )

моя оригинальная версия выше имеет недостаток, как указано Кристоф в комментариях ниже, правильная версия (дополнительный возврат позволяет цеплять):

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

есть ли более компактное и / или более быстрое решение, возможно, в контексте JavaScript-фреймворка? (ни одна из предложенных версий ниже не является более компактной или более быстрой)

есть ли какая-либо структура JavaScript с встроенным вращением массива? (По-прежнему никто не отвечает)

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

в комментариях, Жан поднял вопрос о том, что код не поддерживает перегрузку 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, тогда как различия в 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;
    };
})();

EDIT: см. также мой новый ответ с count аргумент:https://stackoverflow.com/a/33451102

можно использовать push(), pop(), shift() и unshift() функции:

function arrayRotateOne(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'];

Я, вероятно, сделал бы что-то вроде этого:

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
}

пример:

for(let i = -6 ; i <= 6 ; i++)
  console.log( arrayRotate( ["H","e","l","l","o"], i).join(''), 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

многие из этих ответов кажутся слишком сложными и трудными для чтения. Я не думаю, что видел, чтобы кто-то использовал splice с 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;
}


@Christoph, вы сделали чистый код, но 60% медленных чем этот я нашел. Посмотрите на результат на jsPerf:http://jsperf.com/js-rotate-array/2 [Edit] 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

см.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);
  }
}

принятый ответ имеет недостаток в том, что он не может обрабатывать массивы больше размера стека вызовов, который зависит от сеанса, но должен быть около 100~300K элементов. Например, в текущем сеансе Chrome, который я пробовал, это был 250891. Во многих случаях вы можете даже не знать, что размер массива может динамически расти. Так что это серьезная проблема.

чтобы преодолеть это ограничение, я думаю один интересный способ использования Array.prototype.map() и отображение элементов перестановка индексов по кругу. Этот метод принимает один целочисленный аргумент. Если этот аргумент положительный, он будет вращаться по возрастающим индексам, а если отрицательный-по убывающим индексам. Это имеет только 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));

на самом деле после того, как меня критиковали по двум вопросам, как следует;

  1. нет необходимости в условном для положительного или отрицательного ввода, так как он обнаруживает нарушение сухого .вы можете сделать это с одной картой, потому что каждый отрицательный 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));

затем снова; конечно, функторы JS любят Array.prototype.map() медленны по сравнению с их эквивалентами, закодированными в простом JS. Для того, чтобы получить более 100% повышение производительности, вероятно, будет мой выбор Array.prototype.rotate() если мне когда-нибудь понадобится повернуть массив в производственном коде, как тот, который я использовал в своей попытке на 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;
};

вот очень простой способ перемещения элементов в массиве:

function rotate(array, stepsToShift) {

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

    return array;
}

эта функция немного быстрее, чем принятый ответ для небольших массивов, но намного быстрее для больших массивов. Эта функция также допускает произвольное число оборотов, большее длины массива, что является ограничением исходной функции.

наконец, принятый ответ вращается в противоположном направлении, как описано.

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

Вы можете проверить разбивка производительности здесь.


когда я не смог найти готовый фрагмент, чтобы начать список дней с "сегодня", я сделал это так (не совсем общий, вероятно, гораздо менее изысканный, чем приведенные выше примеры, но сделал работу):

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

благодаря небольшому рефакторингу лучшего программиста, чем я, это строка или две короче моей первоначальной попытки, но любые дальнейшие комментарии по эффективности приветствуются.


Как насчет увеличения счетчика, а затем получения оставшейся части деления на длину массива, чтобы получить, где вы должны быть.

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

синтаксис языка в сторону это должно работать нормально.


Если Ваш массив будет большим и / или вы собираетесь много вращаться, вы можете рассмотреть возможность использования связанного списка вместо массива.


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

Я опаздываю, но у меня есть кирпич, чтобы добавить к этим хорошим ответам. Меня попросили закодировать такую функцию, и я сначала сделал:

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

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

вот код golf версия для хихиканья

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

EDIT1::* Внеофисный, реализации mutationless.

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.

нет режиме мутации. Слишком мутация в этих ответах.

кроме того, меньше операций, чем большинство ответов. Ни хлопков, ни толчков, ни сращивания, ни сдвига.

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 затем просто сопоставьте текущий индекс с индексом смещения А.

в противном случае, по модулю смещения и длины для отображения смещения в границах массива.

возьмите, например 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] и запугивали. не будет. мне потребовалось 3 минуты в Repl, чтобы понять это.

  1. мы знаем!--7-- > отрицательно, потому что случай n < 0. Если число больше диапазона массива,n % A.length будет отображать его в диапазоне.
  2. n + A.length добавьте это число в A.length смещать правильное сумма.
  3. мы знаем!--7-- > отрицательно, потому что случай n < 0. n + A.length добавьте это число в A.length компенсировать правильную сумму.
  4. далее сопоставьте его с диапазоном длины A используя дулю. Второй modulous необходимо сопоставить результат расчета в режущих диапазоне

    enter image description here

  5. первый индекс: -4 + 0 = -4. A. Длина = 5. A. Длина - 4 = 1. А2 is 2. Индекс карты от 0 до 2. [2,... ]

  6. следующий индекс, -4 + 1 = -3. 5 + -3 = 2. А2 is 3. Индекс карты от 1 до 3. [2,3... ]
  7. Etc.

то же процесс применяется к offset = 4. Когда offset = -4 и A = [1,2,3,4,5], для каждого индекса, offset + index сделает величину больше.

  1. 4 + 0 = 0. Карты[0] значение по[4]. [5...]
  2. 4 + 1 = 5, 5 вне пределов при индексировании, поэтому сопоставьте2 к значение в оставшейся части 5 / 5, что равно 0. А2 = значение at A[0]. [5,1...]
  3. повторить.

использование спреда ES6 для неизменяемого примера ...

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

и

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

это, вероятно, не самый эффективный, но он лаконичен.