Поворот массива 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-фреймворк с встроенным вращением массива? (До сих пор никто не ответил)

693   17  

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

Edit вот версия мутатора:

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] ОК теперь есть больше браузеров, что не очевидные методы ведьмы лучшие

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

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

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

объяснение:

карта каждого индекса стоимости на индекс смещения. В данном случае

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 для смещения N, необходимая сумма.
  3. мы знаем!--7--> отрицательно, потому что случай n < 0. n + A.length добавьте это число в A.length чтобы компенсировать N правильную сумму.
  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 выходит за пределы при индексации, так что карта a2 к значение в оставшейся части 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)]

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

Comments

    Ничего не найдено.