Поворот элементов в массиве в JavaScript


86

Мне было интересно, какой способ поворота массива 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 со встроенным поворотом массива? (Все еще никто не ответил)


1
Я не понимаю, что должен делать ваш пример. Почему бы вам просто не использовать months[new Date().getMonth()]для получения названия текущего месяца?
Гамбо

1
@Jean: код не работает: как вы это делаете, он отменяет сдвиг соединенных элементов как массив, а не по отдельности; вам нужно будет использовать, apply()чтобы ваша реализация работала
Кристоф

Сегодня ротация изменила бы месяцы, чтобы отобразить этот список (с Decпервой позицией):["Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"]
Жан Винсент

@Christoph, вы правы, это не будет работать как обычная функция поворота. Он работает, только если используется для преобразования в строку сразу после этого.
Жан Винсент,

@Jean: вы можете исправить свою версию с помощью Array.prototype.unshift.apply(this, this.splice(...))- моя версия делает то же самое, но использует push()вместоunshift()
Christoph

Ответы:


60

Типобезопасная универсальная версия, изменяющая массив:

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
Красивое кеширование Array.prototypeметодов! +1
Джеймс

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

Какова стоимость закрытия здесь только для кеширования push и splice?
Жан Винсент,

@Jean: ну, замыкания охватывают всю цепочку областей видимости; до тех пор, пока внешняя функция находится на верхнем уровне, влияние должно быть незначительным и в любом случае оно равно O (1), тогда как поиск методов Array по количеству вызовов составляет O (n); Оптимизация реализаций может встроить поиск, так что никакой выгоды не будет, но с довольно глупыми интерпретаторами, с которыми нам приходилось иметь дело долгое время, кеширование переменных в более низкой области может иметь значительное влияние
Кристоф,

1
При этом не будут вращаться большие массивы. Я проверил сегодня, и он может обрабатывать только массив длиной 250891 элемент. Очевидно, количество аргументов, которые вы можете передать applyметодом, ограничено определенным размером стека вызовов. В терминах ES6 оператор распространения также будет страдать от той же проблемы с размером стека. Ниже я привожу карту, позволяющую сделать то же самое. Это медленнее, но работает для миллионов элементов. Вы также можете реализовать карту с помощью простого цикла for или while, и это станет намного быстрее.
Реду

144

Вы можете использовать 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 🖤🧡💚💙💜


У него нет счета, хотя это не имеет большого значения в зависимости от использования.
JustGage

@JustGage, я публикую еще один ответ с поддержкой подсчета stackoverflow.com/questions/1985260/…
Юкулеле

2
Просто примечание: имейте в виду, что этот метод является навязчивым, и он будет управлять массивом, отправленным в качестве параметра. Нетрудно решить дублирование массива перед его
отправкой

Вот версия в машинописном тексте с левой и правой переменной stackblitz.com/edit/typescript-vtcmhp
Дживо Елич

38

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

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

имейте в виду, что эта функция сохраняет исходный массив неизменным
Кристоф

Ему необходимо изменить исходный массив (this), как это делают push, pop, shift, unshift, concat и splice. В остальном это действительно так.
Жан Винсент,

@ Гамбо, почему цикл while? Нам не нужно делать n положительным, splice также работает с отрицательными значениями. В конце концов, это правильная, но в значительной степени версия Christoph, которая первой сделала все правильно, без предупреждения о перегрузке.
Жан Винсент,

@Gumbo, поправка к моему предыдущему комментарию, отрицательные числа работают только в версии splice (n. This.length). Использование splice (0, n), как в вашей версии, требует положительного int.
Жан Винсент,

1
Я добавил n = n % this.lengthперед оператором return для обработки отрицательных и / или выходящих за границы чисел.
iedmrc

25

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

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

При использовании этой функции я столкнулся с проблемой, потому что она изменяет исходный массив. Я нашел здесь обходные пути: stackoverflow.com/questions/14491405
ognockocaten

1
@ognockocaten Это не проблема, а как работает эта функция. если вы хотите сохранить исходный массив неизменным, var arr2 = arrayRotate(arr.slice(0), 5)
клонируйте

Это действительно отличный ответ, и он мне помог. Однако было бы лучше предоставить альтернативу, которая не изменяет исходный массив. Конечно, просто создать копию оригинала легко, но это не лучшая практика, поскольку она использует память без необходимости.
Гибридный веб-разработчик

Использование @Hybridwebdevconst immutatableArrayRotate = (arr, count) => ArrayRotate(arr.clone(), count)
Юкулеле

8

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

Выводит 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'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

1
Хороший ответ. Это было до того, как вы перешли на темную сторону (PHP)?
Ник

... Я родился на темной стороне.
mickmackusa

:) Кстати, я использовал это здесь
Ник

6

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

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

а также

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

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


5

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

const rotate = (arr, count = 1) => {
  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]


2
Это действительно здорово! Вы можете избежать необходимости проверки count === 0, установив значение по умолчанию. Это позволит вам сделать это однострочным:const rotate = (arr, n = 1) => [...arr.slice(n, arr.length), ...arr.slice(0, n)];
Ник Ф,

@NickF приятно, спасибо! Не думал так.
kashesandr

4

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

function rotate(array, stepsToShift) {

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

    return array;
}

3

@Christoph, вы сделали чистый код, но на 60% медленнее, чем тот, который я нашел. Посмотрите на результат на jsPerf: http://jsperf.com/js-rotate-array/2 [Edit] Хорошо, теперь есть больше браузеров и неочевидные методы, которые лучше всего

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

@molokocolo Интуитивно понятно, что ваше решение будет работать медленнее, поскольку приращение будет выше, потому что вы используете цикл. Я обновил ваш тестовый пример с шагом 5, и, похоже, он работает медленнее: jsperf.com/js-rotate-array/3
Жан Винсент

Я не понимаю, что делает функция rotateArray () ^^, только то, что она работает :) (это странный код!) Chrome ведет себя почти противоположно Firefox ...
molokoloco

Этот метод очень интересен, он работает, меняя местами ссылки на элементы в массиве. Размер массива никогда не меняется, поэтому он работает очень быстро с недостатком использования циклов. Интересно то, что это показывает, что Firefox - самый быстрый в циклах, а хром - самый быстрый в манипуляциях с массивами.
Жан Винсент,

1
Проанализировав код, я обнаружил слабое место, которое показывает, что это решение работает быстрее только для небольших массивов и определенного количества вращений: jsperf.com/js-rotate-array/5 Среди всех браузеров самым быстрым остается Google Chrome с функцией splice / unshift. решение. Это означает, что это действительно вопрос оптимизации метода массива, который Chrome делает лучше, чем другие браузеры.
Жан Винсент,

3

см. 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

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

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

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


3
function rotate(arr, k) {
for (var i = 0; i < k+1; i++) {
    arr.push(arr.shift());
}
return arr;
}
//k work as an index array
console.log(rotate([1, 2, 7, 4, 5, 6, 7], 3)); //[5,6,7,1,2,7,4]
console.log(rotate([-1, -100, 3, 99], 2));     //[99,-1,-100,3]

3
// Example of array to rotate
let arr = ['E', 'l', 'e', 'p', 'h', 'a', 'n', 't'];

// Getting array length
let length = arr.length;

// rotation < 0 (move left), rotation > 0 (move right)
let rotation = 5;

// Slicing array in two parts
let first  = arr.slice(   (length - rotation) % length, length); //['p', 'h', 'a' ,'n', 't']
let second = arr.slice(0, (length - rotation) % length); //['E', 'l', 'e']

// Rotated element
let rotated = [...first, ...second]; // ['p', 'h', 'a' ,'n', 't', 'E', 'l', 'e']

В одной строке кода:

let rotated = [...arr.slice((length - rotation) % length, length), ...arr.slice(0, (length - rotation) % length)];

2

У принятого ответа есть недостаток, заключающийся в том, что он не может обрабатывать массивы, превышающие размер стека вызовов, который зависит от сеанса, но должен составлять около 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. Нет необходимости в условном выражении для положительного или отрицательного ввода, поскольку оно выявляет нарушение DRY. Вы можете сделать это с одной картой, потому что каждое отрицательное n имеет положительный эквивалент (Совершенно верно ...)
  2. Функция Array должна либо изменить текущий массив, либо создать новый массив, ваша функция может делать это либо в зависимости от того, нужен ли сдвиг или нет (Совершенно верно ..)

Я решил изменить код следующим образом;

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

shift () и splice (), сколь бы медленными они ни были, на самом деле быстрее, чем использование map () или любого метода, использующего параметр функции. shift () и splice (), будучи более декларативными, также легче оптимизировать в долгосрочной перспективе.
Жан Винсент,

@Jean Vincent Да, вы правы ... карта на самом деле может оказаться медленнее, но я считаю, что идеальная логика вращения такова. Я просто попытался показать логичный подход. Карту можно легко упростить с помощью цикла for, что приведет к значительному увеличению скорости ... Однако на самом деле в принятом ответе есть подход, разработанный с учетом некоторых других языков. Он не идеален для JS ... даже не будет работать, если размер массива больше, чем размер стека вызовов. (в моем случае и на этом сеансе оказалось, что он ограничен всего 250891 предметом) Так вот что ..!
Реду

Вы правы в отношении размера стека вызовов, который может превышать размер применяемого стека, это более веский аргумент, чем скорость, рассмотрите возможность редактирования своего ответа, чтобы подчеркнуть этот момент с помощью улучшенного отступа.
Jean Vincent

2

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

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

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

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

По-прежнему работает с отрицательным 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))

РЕДАКТИРОВАТЬ1 :: * и мутаций.

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

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)) меньше.

Давайте сначала объясним расчет индекса отрицательного 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чтобы компенсировать n правильной суммы.
  3. Мы знаем, что nэто отрицательно, потому что это так n < 0. n + A.lengthдобавить это число кA.length чтобы компенсировать n правильной суммы.
  4. Затем сопоставьте его с диапазоном длины A по модулю. Второй модуль необходим для отображения результата расчета в индексируемый диапазон.

    введите описание изображения здесь

  5. Первый индекс: -4 + 0 = -4. A.length = 5. A.length - 4 = 1. A 2 равно 2. Сопоставьте индекс от 0 до 2.[2,... ]

  6. Следующий индекс -4 + 1 = -3. 5 + -3 = 2. 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. повторение.

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


1

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

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

1

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

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

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

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


2
Это никоим образом не поворачивает массив.
Жан Винсент,

1
Верно (как указано в ответе), но это избавляет от необходимости вращать массив.
tgandrews 01

1
Но весь смысл этого состоит в том, чтобы вращать массив по заголовку, а не отображать массив с определенного смещения. Если бы вы использовали тот же цикл для перестройки нового массива, он был бы ужасно медленнее, чем другие версии, использующие собственные методы Array. К тому же вы забыли выйти из бесконечного цикла.
Жан Винсент

0

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


Согласен, но вопрос относится к массивам, а точнее к расширению глаголов Array.
Жан Винсент

0

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

Это не поворачивает массив. Чтобы ответить на этот вопрос, вам нужно вернуть массив, в котором находится первый элемент на счетчике.
Jean Vincent

0

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

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

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

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

Родной, быстрый, небольшой, семантический, работает на старых движках и "каррибируемый".

function rotateArray(offset, array) {
    offset = -(offset % array.length) | 0 // ensure int
    return array.slice(offset).concat(
        array.slice(0, offset)
    )
}

0

** Используя последнюю версию JS, мы можем легко его собрать **

 Array.prototype.rotateLeft = function (n) {
   this.unshift(...this.splice(-(n), n));
    return this
  }

здесь движется: количество вращений , массив, который вы можете передать случайное число

let a = [1, 2, 3, 4, 5, 6, 7];
let moves = 4;
let output = a.rotateLeft(moves);
console.log("Result:", output)

0

Arrayв JS ниже есть встроенный метод, который можно использовать для вращения массива довольно легко, и, очевидно, эти методы неизменны по своей природе.

  • push: Вставляет элемент в конец массива.
  • pop: Удаляет элемент из конца массива.
  • unshift: Вставляет элемент в начало массива.
  • shift: Удаляет элемент из начала массива.

Приведенное ниже решение ( ES6) принимает два аргумента: массив должен быть повернут и n, сколько раз массив должен быть повернут.

const rotateArray = (arr, n) => {
  while(arr.length && n--) {
    arr.unshift(arr.pop());
  }
  return arr;
}

rotateArray(['stack', 'overflow', 'is', 'Awesome'], 2) 
// ["is", "Awesome", "stack", "overflow"]

Его можно добавить в Array.prototype и использовать во всем приложении.

Array.prototype.rotate = function(n) {
 while(this.length && n--) {
   this.unshift(this.pop());
 }
 return this;
}
[1,2,3,4].rotate(3); //[2, 3, 4, 1]

0

Использование цикла for. Вот шаги

  1. Сохраните первый элемент массива как временную переменную.
  2. Затем поменяйте местами слева направо.
  3. Затем назначьте временную переменную последнему элементу массива.
  4. Повторите эти шаги для количества оборотов.

function rotateLeft(arr, rotations) {
    let len = arr.length;
    for(let i=0; i<rotations; i++){ 
        let temp = arr[0];
        for(let i=0; i< len; i++){
            arr[i]=arr[i+1];
        }
        arr[len-1]=temp;
    }
    return arr;
}

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

let rotations = 3;
let output = rotateLeft(arr, rotations);
console.log("Result Array => ", output);



-2

Не уверен в эффективности, но я бы сделал это без мутаций:

	Array.prototype.rotate = function( n ) {
  
		 return this.map( (item, index)=> this[ (this.length + index + n)%this.length ] )
	}


Итак, первая часть вашего ответа «Не уверен в эффективности» - это как сказать «Я не знаю, отвечает ли это вообще на ваш вопрос». В исходном вопросе не задается вопрос «как лучше всего повернуть массив», а конкретно задается вопрос о компактности и скорости (эффективность памяти и времени соответственно). Вопрос в эффективности, и вы не ответили на этот компонент.
richardpringle,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.