Подтвердить что ты не робот

JavaScript Array 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() )

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

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

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

Есть ли какая-либо инфраструктура JavaScript со встроенным вращением массива? (До сих пор никто не ответил)

4b9b3361

Ответ 1

Типовая, универсальная версия, которая мутирует массив:

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

Ответ 2

Вы можете использовать методы 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 аргумент, смотрите мой другой ответ: fooobar.com/questions/131592/...

Ответ 3

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

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

Ответ 4

Эта функция работает в обоих направлениях и работает с любым числом (даже если число больше длины массива):

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(["🧡","💚","💙","💜","🖤"], i), i);
}

результат:

[ "🖤", "🧡", "💚", "💙", "💜" ]    -6
[ "🧡", "💚", "💙", "💜", "🖤" ]    -5
[ "💚", "💙", "💜", "🖤", "🧡" ]    -4
[ "💙", "💜", "🖤", "🧡", "💚" ]    -3
[ "💜", "🖤", "🧡", "💚", "💙" ]    -2
[ "🖤", "🧡", "💚", "💙", "💜" ]    -1
[ "🧡", "💚", "💙", "💜", "🖤" ]    0
[ "💚", "💙", "💜", "🖤", "🧡" ]    1
[ "💙", "💜", "🖤", "🧡", "💚" ]    2
[ "💜", "🖤", "🧡", "💚", "💙" ]    3
[ "🖤", "🧡", "💚", "💙", "💜" ]    4
[ "🧡", "💚", "💙", "💜", "🖤" ]    5
[ "💚", "💙", "💜", "🖤", "🧡" ]    6

Ответ 5

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

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

Что касается компактности, я могу предложить пару общих однострочных функций (не считая части console.log | return). Просто подайте его массив и целевое значение в аргументы.

Я объединяю эти функции в одну для четырехкарточной карточной игры, где массив ['N', 'E', 'S', 'W']. Я оставил их отдельными в случае, если кто-то хочет скопировать/вставить для своих нужд. Для моих целей я использую функции при поиске, чей ход близок к игре/действию на разных этапах игры (Pinochle). Я не потрудился проверить скорость, поэтому, если кто-то еще захочет, не стесняйтесь сообщить мне результаты.

*, единственное различие между функциями - "+1".

function rotateToFirst(arr,val){  // val is Trump Declarer 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 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);

adjustedArray =

W,N,E,S

Ответ 6

@Кристоф, вы сделали чистый код, но на 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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

Принятый ответ имеет недостаток, заключающийся в том, что он не может обрабатывать массивы большего размера, чем размер стека вызовов, который зависит от сеанса, но должен быть примерно как 100 ~ 300 тыс. элементов. Например, в текущем сеансе 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));

Ответ 10

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

function rotate(array, stepsToShift) {

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

    return array;
}

Ответ 11

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

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

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

Здесь вы можете проверить производительность.

Ответ 12

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

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

а также

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

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

Ответ 13

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

Ответ 14

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

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

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

Ответ 15

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

Ответ 16

@molokoloco Мне нужна была функция, которую я мог бы настроить для вращения в направлении - true для forward и false для backward. Я создал фрагмент, который принимает направление, счетчик и массив и выводит объект с счетчиком, увеличенным в соответствующем направлении, а также предыдущим, текущим и следующими значениями. Он НЕ изменяет исходный массив.

Я также привязал его к вашему фрагменту, и хотя он не быстрее, он быстрее, чем те, с которыми вы сравниваете свои, - на 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)

Ответ 17

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

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

Ответ 18

РЕДАКТИРОВАТЬ :: Эй, получается, слишком много итераций происходит. Нет петель, нет ветвления.

Все еще работает с отрицательным 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 :: * Реализация без ответвлений, без мутаций.

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

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

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

Возьмем, например, offset = 4 и offset = -4.

Когда offset = -4 и A = [1,2,3,4,5], для каждого индекса offset + index будет Math.abs(offset) величину (или Math.abs(offset)).

Поясним сначала расчет индекса отрицательного n. A[(((n % A.length) + A.length) % A.length)+0] и были запуганы. Не будь Мне потребовалось 3 минуты в Repl, чтобы решить это.

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

    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 будет offset = -4 величину.

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

Ответ 19

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

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

Ответ 20

Не мутирующее решение

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

Ответ 21

Я делюсь своим решением, которое я использую для вращения на карусели. Он может сломаться, когда размер массива меньше, чем 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]

Ответ 22

Простое решение с ломтиком и деструктуризацией:

const rotate = (arr, count) => {
	if (count === 0) return arr;
  return [...arr.slice(count, arr.length), ...arr.slice(0, count)];
};

const arr = [1,2,3,4,5];

console.log(rotate(arr, 1));  // [2, 3, 4, 5, 1]
console.log(rotate(arr, 2));  // [3, 4, 5, 1, 2]
console.log(rotate(arr, -2)); // [4, 5, 1, 2, 3]
console.log(rotate(arr, -1)); // [5, 1, 2, 3, 4]