Поверніть елементи в масиві в 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()
Крістоф

Відповіді:


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
Джеймс

Дуже приємна куленепробивна реалізація, але загалом я б віддав перевагу покладатися на винятки або просто невдалі відповіді для поганого використання. Це робить код чистим і швидким. Користувач несе відповідальність за передачу правильних параметрів або оплату наслідків. Мені не подобається покарання за хороших користувачів. Крім цього, це ідеально, оскільки він модифікує масив відповідно до запиту, і він не страждає від вади моєї реалізації, яка змістила масив замість окремих елементів, як ви зазначили. Повернення цього також краще дозволити ланцюжку. Тож спасибі.
Жан Вінсент

Яка вартість закриття тут, просто для кешування та з’єднання?
Жан Вінсент

@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 / ...
Yukulélé

2
Тільки примітка: пам’ятайте, що цей метод є нав'язливим, і він буде маніпулювати масивом, надісланим як параметр. Не складно вирішити дублювання масиву перед його відправленням
fguillen

Ось версія в машинописі з лівою правою змінною stackblitz.com/edit/typescript-vtcmhp
Dživo Jelić,

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. Крім цього, це дійсно.
Жан Вінсент

@Gumbo, чому цикл while? Нам не потрібно робити n позитивними, сплайсинг працює з негативними значеннями так само добре. Врешті-решт, це правильна, але в значній мірі версія Крістофа, яка спершу зрозуміла це без перевантажувальних застережень.
Жан Вінсент

@Gumbo, виправлення мого попереднього коментаря, від’ємні числа працюють лише на сплайсинг (n. This.length). Використання сплайсингу (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)
Yukulélé

Це справді чудова відповідь, і це мені допомогло. Однак найкращим буде надання альтернативи, яка не мутує вихідний масив. Звичайно, просто створити копію оригіналу просто, це не найкраща практика, оскільки вона використовує пам’ять без потреби.
гібридної мережі

@Hybridwebdev useconst immutatableArrayRotate = (arr, count) => ArrayRotate(arr.clone(), count)
Yukulélé

8

Тому багато з цих відповідей здаються надто складними та важкими для читання. Думаю, я не бачив, щоб хтось використовував сплайсинг з конкатом ...

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 | повернення частини). Просто подайте це масив і цільове значення в аргументах.

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

* зауважте, що єдиною різницею між функціями є "+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 приємно, дякую! Не думав так.
кашесандр

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 [Редагувати] ОК

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 найшвидший у циклах, тоді як chrome найшвидший у маніпуляціях з масивами.
Jean Vincent

1
Проаналізувавши код, я виявив слабку сторону, яка показує, що це рішення є швидшим лише для менших масивів та певної кількості обертань: jsperf.com/js-rotate-array/5 Серед усіх браузерів найшвидшим залишається Google Chrome зі зрощуванням / зрушенням рішення. Це означає, що це справді питання оптимізації методу Array, який Chrome робить краще, ніж інші браузери.
Jean Vincent

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
// 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. Немає необхідності в умовному для позитивного чи негативного вводу, оскільки він виявляє порушення СУХОГО. Ви можете зробити це за допомогою однієї карти, оскільки кожен від’ємний 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 () і сплайсинг (), настільки ж повільними, наскільки вони можуть бути, насправді швидші, ніж використання map () або будь-якого методу, що використовує параметр функції. shift () і splice (), які є більш декларативними, також простіше оптимізувати в довгостроковій перспективі.
Жан Вінсент

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

Ви маєте рацію щодо розміру стека дзвінків, який застосовується, може перевищувати, це більш вагомий аргумент, ніж швидкість, розгляньте можливість редагування своєї відповіді, щоб підкреслити цей момент також покращеним відступом.
Жан Вінсент

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

Ви можете ознайомитися з розподілом продуктивності тут.


На жаль, це не працює, він зменшує масив до нульового розміру, що пояснює, чому це набагато швидше після першого запуску, ніж будь-яка інша правильна реалізація, особливо на більших масивах. У вашому тестовому тесті, якщо ви поміняєте порядок тестів і відобразите a.length в кінці, ви побачите проблему.
Жан Вінсент

2

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 :: * Впровадження без розгалуження, без мутацій.

Отже, привіт, виявляється, у мене була гілка, де вона мені не потрібна. Ось робоче рішення. від’ємне число = повернути вправо на | число | додатне число = повернути ліворуч на число

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. Ми знаємо, що 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. А. довжина = 5. А. довжина - 4 = 1. А 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. повторити.

2
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]

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

1
Але вся суть цього ІС - обертати масив відповідно до заголовка, а не відображати масив з певного зміщення. Якби ви використали той самий цикл для перебудови нового масиву, це було б страшно повільніше, ніж інші версії, використовуючи власні методи Array. Плюс ти забув вирватися з нескінченної петлі.
Жан Вінсент

0

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


Погодився, але питання стосується масивів, а точніше розширення дієслів масиву.
Жан Вінсент

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)

Це не обертає масив. Щоб відповісти на це запитання, вам потрібно повернути масив, де перший елемент поставлений у лічильник.
Жан Вінсент

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. Потім призначте змінну temp останньому елементу масиву.
  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.