Rotar los elementos en una matriz en JavaScript

86

Me preguntaba cuál era la forma más eficiente de rotar una matriz de JavaScript.

Se me ocurrió esta solución, donde un positivo nrota la matriz hacia la derecha y un negativo nhacia la izquierda ( -length < n < length):

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

Que luego se puede usar de esta manera:

var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
months.rotate( new Date().getMonth() );

Mi versión original anterior tiene un defecto, como lo señaló Christoph en los comentarios a continuación, una versión correcta es (la devolución adicional permite el encadenamiento):

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

¿Existe una solución más compacta y / o más rápida, posiblemente en el contexto de un marco de JavaScript? (ninguna de las versiones propuestas a continuación es más compacta ni más rápida)

¿Existe algún marco de JavaScript con una rotación de matriz incorporada? (Todavía no ha respondido nadie)

Jean Vincent
fuente
1
No entiendo lo que debería hacer tu ejemplo. ¿Por qué no usas months[new Date().getMonth()]para obtener el nombre del mes actual?
Gumbo
1
@Jean: el código está roto: de la forma en que lo hace, deshará los elementos empalmados como una matriz y no individualmente; que tendrá que usar apply()para que su implementación funcione
Christoph
Hoy la rotación modificaría meses para mostrar esta lista (con Decen la primera posición):["Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"]
Jean Vincent
@Christoph, tienes razón, esto no funcionaría como una función de rotación genérica. Solo funciona si se usa para convertir a una cadena inmediatamente después.
Jean Vincent
@Jean: puedes arreglar tu versión a través de Array.prototype.unshift.apply(this, this.splice(...))- mi versión hace lo mismo, pero usa en push()lugar deunshift()
Christoph

Respuestas:

60

Versión genérica con seguridad de tipos que muta la matriz:

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

En los comentarios, Jean planteó el problema de que el código no admite la sobrecarga de push()y splice(). No creo que esto sea realmente útil (ver comentarios), pero una solución rápida (aunque algo así como un truco) sería reemplazar la línea

push.apply(this, splice.call(this, 0, count));

Con este:

(this.push || push).apply(this, (this.splice || splice).call(this, 0, count));

Usar en unshift()lugar de push()es casi dos veces más rápido en Opera 10, mientras que las diferencias en FF fueron insignificantes; el código:

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;
    };
})();
Christoph
fuente
2
¡Buen almacenamiento en caché de Array.prototypemétodos! +1
James
Muy buena implementación a prueba de balas, pero en general preferiría confiar en las excepciones, o simplemente en malas respuestas, para un mal uso. Esto para mantener el código limpio y rápido. Es responsabilidad del usuario pasar los parámetros correctos o pagar las consecuencias. No me gusta la pena para los buenos usuarios. Aparte de eso, esto es perfecto porque modifica la matriz según lo solicitado y no sufre la falla en mi implementación que no desplazó una matriz en lugar de los elementos individuales como señaló. Devolver esto también es mejor para permitir el encadenamiento. Así que gracias.
Jean Vincent
¿Cuál es el costo del cierre aquí, solo para cachear empujar y empalmar?
Jean Vincent
@Jean: bueno, los cierres capturan toda la cadena de alcance; siempre que la función externa sea de nivel superior, el impacto debería ser insignificante y es O (1) de todos modos, mientras que buscar los métodos Array es O (n) en el número de invocaciones; La optimización de las implementaciones podría alinear la búsqueda para que no haya ninguna ganancia, pero con los intérpretes bastante estúpidos con los que hemos tenido que lidiar durante mucho tiempo, el almacenamiento en caché de las variables en un alcance inferior podría tener un impacto significativo
Christoph
1
Esto no rotará matrices grandes. Lo verifiqué hoy y solo podía manejar una matriz de 250891 elementos de longitud. Aparentemente, la cantidad de argumentos que puede pasar por applymétodo está limitada con un cierto tamaño de pila de llamadas. En términos de ES6, el operador de propagación también sufriría el mismo problema de tamaño de pila. A continuación, doy un método de mapa para hacer lo mismo. Es más lento pero funciona para millones de artículos. También puede implementar el mapa con un bucle for o while simple y será mucho más rápido.
Reducción el
144

Se puede utilizar push(), pop(), shift()y unshift()métodos:

function arrayRotate(arr, reverse) {
  if (reverse) arr.unshift(arr.pop());
  else arr.push(arr.shift());
  return arr;
}

uso:

arrayRotate(['h','e','l','l','o']);       // ['e','l','l','o','h'];
arrayRotate(['h','e','l','l','o'], true); // ['o','h','e','l','l'];

Si necesita un countargumento, vea mi otra respuesta: https://stackoverflow.com/a/33451102 🖤🧡💚💙💜

Yukulélé
fuente
No tiene un recuento, aunque eso no es un gran problema dependiendo del uso.
JustGage
@JustGage, publico otra respuesta con soporte de conteo stackoverflow.com/questions/1985260/…
Yukulélé
2
Solo una nota: tenga en cuenta que este método es intrusivo y manipulará la matriz enviada como parámetro. No es difícil resolver la duplicación de la matriz antes de enviarla
fguillen
Aquí hay una versión mecanografiada con la variable izquierda derecha stackblitz.com/edit/typescript-vtcmhp
Dživo Jelić
38

Probablemente haría algo como esto:

Array.prototype.rotate = function(n) {
    return this.slice(n, this.length).concat(this.slice(0, n));
}

Editar     Aquí hay una versión de mutador:

Array.prototype.rotate = function(n) {
    while (this.length && n < 0) n += this.length;
    this.push.apply(this, this.splice(0, n));
    return this;
}
Gumbo
fuente
tenga en cuenta que esta función mantiene la matriz original sin cambios
Christoph
Necesita modificar la matriz original (esto), al igual que lo hacen push, pop, shift, unshift, concat y splice. Aparte de eso, esto es válido.
Jean Vincent
@Gumbo, ¿por qué el bucle while? No necesitamos hacer n positivo, el empalme también funciona con valores negativos. Para el final, esto es correcto, pero prácticamente la versión de Christoph, que lo hizo bien primero sin la advertencia de sobrecarga.
Jean Vincent
@Gumbo, corrección en mi comentario anterior, los números negativos funcionan solo en la versión de empalme (n. This.length). El uso de splice (0, n) como en su versión, requiere un int positivo.
Jean Vincent
1
He agregado n = n % this.lengthantes de la declaración de devolución para manejar números negativos y / o fuera de los límites.
iedmrc
25

Esta función funciona en ambos sentidos y funciona con cualquier número (incluso con un número mayor que la longitud de la matriz):

function arrayRotate(arr, count) {
  count -= arr.length * Math.floor(count / arr.length);
  arr.push.apply(arr, arr.splice(0, count));
  return arr;
}

uso:

for(let i = -6 ; i <= 6 ; i++) {
  console.log(arrayRotate(["🧡","💚","💙","💜","🖤"], i), i);
}

resultado:

[ "🖤", "🧡", "💚", "💙", "💜" ]    -6
[ "🧡", "💚", "💙", "💜", "🖤" ]    -5
[ "💚", "💙", "💜", "🖤", "🧡" ]    -4
[ "💙", "💜", "🖤", "🧡", "💚" ]    -3
[ "💜", "🖤", "🧡", "💚", "💙" ]    -2
[ "🖤", "🧡", "💚", "💙", "💜" ]    -1
[ "🧡", "💚", "💙", "💜", "🖤" ]    0
[ "💚", "💙", "💜", "🖤", "🧡" ]    1
[ "💙", "💜", "🖤", "🧡", "💚" ]    2
[ "💜", "🖤", "🧡", "💚", "💙" ]    3
[ "🖤", "🧡", "💚", "💙", "💜" ]    4
[ "🧡", "💚", "💙", "💜", "🖤" ]    5
[ "💚", "💙", "💜", "🖤", "🧡" ]    6
Yukulélé
fuente
Al usar esta función, encontré un problema porque altera la matriz original. Encontré algunas soluciones aquí: stackoverflow.com/questions/14491405
ognockocaten
1
@ognockocaten No es un problema, es cómo funciona esta función. si desea mantener inalterada la matriz original, var arr2 = arrayRotate(arr.slice(0), 5)
clónela
De hecho, esta es una gran respuesta y me ayudó. Sin embargo, lo mejor sería proporcionar una alternativa que no mute la matriz original. Claro que es fácil simplemente crear una copia del original, esta no es la mejor práctica ya que usa la memoria innecesariamente.
Desarrollador web híbrido
@Hybridwebdev useconst immutatableArrayRotate = (arr, count) => ArrayRotate(arr.clone(), count)
Yukulélé
8

Muchas de estas respuestas parecen demasiado complicadas y difíciles de leer. No creo que vi a nadie usando empalme con 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;
}

Salidas de console.log (* generado en mayo):

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

En cuanto a la compacidad, puedo ofrecer un par de funciones genéricas de una sola línea (sin contar la parte console.log | return). Simplemente alimente la matriz y el valor objetivo en los argumentos.

Combino estas funciones en una para un programa de juego de cartas de cuatro jugadores donde la matriz es ['N', 'E', 'S', 'W']. Los dejé separados en caso de que alguien quiera copiar / pegar para sus necesidades. Para mis propósitos, utilizo las funciones cuando busco quién es el próximo turno para jugar / actuar durante las diferentes fases del juego (Pinochle). No me he molestado en probar la velocidad, así que si alguien más quiere, no dudes en hacerme saber los resultados.

* aviso, la única diferencia entre las funciones es el "+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;
}

función de combinación ...

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

connectedArray =

W,N,E,S
mickmackusa
fuente
1
Buena respuesta. ¿Fue de antes de pasar al lado oscuro (PHP)?
Nick hace
... Nací en el lado oscuro.
mickmackusa hace
:) Por cierto, lo usé aquí
Nick hace
6

Usando la extensión de ES6 para un ejemplo inmutable ...

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

y

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

Probablemente no sea el más eficiente, pero es conciso.

Dudley Craig
fuente
5

Fácil solución con corte y desestructuración:

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]

Kashesandr
fuente
2
¡Esto es realmente bueno! Puede evitar la necesidad de comprobar si count === 0establece un valor predeterminado. Eso le permitiría hacer de esto una sola línea:const rotate = (arr, n = 1) => [...arr.slice(n, arr.length), ...arr.slice(0, n)];
Nick F
@NickF agradable, ¡gracias! No pensé de esta manera.
kashesandr
4

Aquí hay una forma muy simple de cambiar elementos en una matriz:

function rotate(array, stepsToShift) {

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

    return array;
}
aryeh
fuente
3

@Christoph, has hecho un código limpio, pero 60% más lento que este que encontré. Mire el resultado en jsPerf: http://jsperf.com/js-rotate-array/2 [Editar] Bien, ahora hay más navegadores y los mejores métodos no son obvios.

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
molokoloco
fuente
@molokocolo Intuitivamente, su solución funcionaría más lentamente ya que el incremento sería mayor porque está usando un bucle. He actualizado su caso de prueba con un incremento de 5 y parece que luego se ejecuta más lento: jsperf.com/js-rotate-array/3
Jean Vincent
No me imagino qué está haciendo la función rotateArray () ^^ solo que funciona :) (¡es un código extraño!) Chrome se comporta casi al revés de Firefox ...
molokoloco
Este método es muy interesante, funciona intercambiando referencias de elementos en la matriz. El tamaño de la matriz nunca cambia, por lo que es muy rápido con el inconveniente de usar bucles. Lo interesante que muestra es que Firefox es el más rápido en bucles, mientras que Chrome es el más rápido en manipulaciones de matrices.
Jean Vincent
1
Después de analizar el código, encontré una debilidad que muestra que esta solución solo es más rápida para matrices más pequeñas y ciertos recuentos de rotación: jsperf.com/js-rotate-array/5 Entre todos los navegadores, el más rápido sigue siendo Google Chrome con el empalme / deshielo solución. Esto significa que esto es realmente una cuestión de optimización del método Array que Chrome hace mejor que otros navegadores.
Jean Vincent
3

ver 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);
  }
}
Vic99999
fuente
3

Cuando no pude encontrar un fragmento listo para comenzar una lista de días con 'hoy', lo hice así (no del todo genérico, probablemente mucho menos refinado que los ejemplos anteriores, pero hice el trabajo):

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

Gracias a una pequeña refactorización de un programador mejor que yo, es una línea o dos más corta que mi intento inicial, pero cualquier comentario adicional sobre la eficiencia es bienvenido.

Dave Everitt
fuente
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]
moefarsh
fuente
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']

En una línea de código:

let rotated = [...arr.slice((length - rotation) % length, length), ...arr.slice(0, (length - rotation) % length)];
Erik Martín Jordán
fuente
2

La respuesta aceptada tiene el defecto de no poder manejar matrices más grandes que el tamaño de la pila de llamadas, que depende de la sesión, pero debería tener alrededor de 100 ~ 300K elementos. Por ejemplo, en la sesión actual de Chrome que probé fue 250891. En muchos casos, es posible que ni siquiera sepa hasta qué tamaño puede crecer dinámicamente la matriz. Entonces ese es un problema serio.

Para superar esta limitación, supongo que un método interesante es utilizar Array.prototype.map()y mapear los elementos reordenando los índices de forma circular. Este método toma un argumento entero. Si este argumento es positivo, rotará en índices crecientes y si es negativo en la dirección de índices decrecientes. Esto tiene una complejidad de solo O (n) tiempo y devolverá una nueva matriz sin mutar la que se le pide mientras maneja millones de elementos sin ningún problema. Veamos cómo funciona;

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

En realidad, después de haber sido criticado por dos asuntos como sigue;

  1. No hay necesidad de un condicional para entrada positiva o negativa ya que revela una violación de DRY. Podrías hacer esto con un mapa porque cada n negativa tiene un equivalente positivo (Totalmente correcto ..)
  2. Una función de matriz debería cambiar la matriz actual o crear una nueva matriz, su función podría hacerlo dependiendo de si es necesario un cambio o no (Totalmente correcto ..)

He decidido modificar el código de la siguiente manera;

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

Entonces otra vez; por supuesto, los functores JS como Array.prototype.map()son lentos en comparación con sus equivalentes codificados en JS simple. Para obtener un aumento de rendimiento de más del 100%, lo siguiente probablemente sería mi elección Array.prototype.rotate()si alguna vez necesito rotar una matriz en el código de producción como la que usé en mi intento deString.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;
};
Redu
fuente
shift () y splice (), por lentos que sean, en realidad son más rápidos que usar map () o cualquier método que use un parámetro de función. shift () y splice () son más declarativos y también son más fáciles de optimizar a largo plazo.
Jean Vincent
@Jean Vincent Sí, tienes razón ... el mapa de hecho podría resultar más lento, pero creo que la lógica de rotación ideal es esta. Solo intenté mostrar el enfoque lógico. El mapa se puede simplificar fácilmente con un bucle for y daría como resultado una mejora significativa en la velocidad ... Sin embargo, lo real es que la respuesta aceptada tiene un enfoque desarrollado con algunos otros idiomas en mente. No es ideal para JS ... ni siquiera se ejecutará cuando el tamaño de la matriz sea mayor que el tamaño de la pila de llamadas. (en mi caso y en esta sesión resultó estar limitado con solo 250891 ítems) ¡Así que ahí está ...!
Reducción el
Tiene razón sobre el tamaño de la pila de llamadas que se aplica puede exceder, este es un argumento más válido que la velocidad, considere editar su respuesta para enfatizar este punto con una sangría mejorada también.
Jean Vincent
2

Esta función es un poco más rápida que la respuesta aceptada para arreglos pequeños pero MUCHO más rápida para arreglos grandes. Esta función también permite un número arbitrario de rotaciones mayor que la longitud de la matriz, lo cual es una limitación de la función original.

Por último, la respuesta aceptada gira en la dirección opuesta a la descrita.

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

Y el equivalente funcional (que parece tener también algunos beneficios de rendimiento):

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

Puede consultar el desglose de rendimiento aquí.

Tanner Stults
fuente
Desafortunadamente, no funciona, reduce la matriz al tamaño cero, lo que explica por qué es mucho más rápido, después de la primera ejecución, que cualquier otra implementación correcta, especialmente en matrices más grandes. En su prueba de referencia, si cambia el orden de las pruebas y muestra una longitud al final, verá el problema.
Jean Vincent
2

EDITAR :: Oye, resulta que hay demasiada iteración. Sin bucles, sin ramificaciones.

Todavía funciona con n negativo para la rotación a la derecha y n positivo para la rotación a la izquierda para cualquier tamaño n, sin mutaciones

function rotate(A,n,l=A.length) {
  const offset = (((n % l) + l) %l)
  return A.slice(offset).concat(A.slice(0,offset))
}

Aquí está la versión de código de golf para risitas

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

EDIT1 :: * Implementación sin ramificaciones, sin mutaciones.

Bueno, resulta que tenía una sucursal donde no la necesitaba. Aquí hay una solución funcional. num negativo = girar a la derecha por | num | num positivo = izquierda rota en num

function r(A,n,l=A.length) {
  return A.map((x,i,a) => A[(((n+i)%l) + l) % l])
}

La ecuación ((n%l) + l) % lmapea exactamente números positivos y negativos de cualquier valor arbitrariamente grande de n

ORIGINAL

Gire a la izquierda y a la derecha. Gire a la izquierda con positivo n, gire a la derecha con negativo n.

Funciona para entradas obscenamente grandes de n.

Sin modo de mutación. Demasiada mutación en estas respuestas.

Además, menos operaciones que la mayoría de las respuestas. Sin pop, sin empuje, sin empalmes, sin cambios.

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

o

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

Explicación:

mapee cada índice de A al valor en el desplazamiento del índice. En este caso

offset = num

si el offset < 0entonces offset + index + positive length of Aapuntará al desplazamiento inverso.

si offset > 0 and offset < length of Aentonces simplemente mapee el índice actual al índice de desplazamiento de A.

De lo contrario, module el desplazamiento y la longitud para asignar el desplazamiento en los límites de la matriz.

Tomemos, por ejemplo, offset = 4y offset = -4.

Cuándo offset = -4, y A = [1,2,3,4,5], para cada índice, offset + indexhará que la magnitud (o Math.abs(offset)) sea menor.

Primero expliquemos el cálculo del índice de n negativo. A[(((n % A.length) + A.length) % A.length)+0]y ha sido intimidado. No lo estés. Me tomó 3 minutos en una respuesta para resolverlo.

  1. Sabemos que nes negativo porque el caso lo es n < 0. Si el número es mayor que el rango de la matriz, n % A.lengthlo mapeará en el rango.
  2. n + A.lengthsume ese número para A.lengthcompensar n la cantidad correcta.
  3. Sabemos que nes negativo porque el caso lo es n < 0. n + A.lengthagrega ese número aA.length compensar n la cantidad correcta.
  4. Siguiente Asignelo al rango de la longitud de A usando módulo. El segundo módulo es necesario para mapear el resultado del cálculo en un rango indexable

    ingrese la descripción de la imagen aquí

  5. Primer índice: -4 + 0 = -4. A.length = 5. A.length - 4 = 1. A 2 es 2. Índice de mapa de 0 a 2.[2,... ]

  6. Siguiente índice, -4 + 1 = -3. 5 + -3 = 2. A 2 es 3. Mapa del índice 1 a 3.[2,3... ]
  7. Etc.

El mismo proceso se aplica a offset = 4. Cuándo offset = -4, y A = [1,2,3,4,5], para cada índice, offset + indexaumentará la magnitud.

  1. 4 + 0 = 0. Asigne A [0] al valor en A [4].[5...]
  2. 4 + 1 = 5, 5 está fuera de los límites al indexar, por lo tanto, asigne A 2 al valor en el resto de 5 / 5, que es 0. A 2 = valor en A [0].[5,1...]
  3. repetir.
nathan rogers
fuente
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));

Bhaskar Mishra
fuente
1

Solución no mutante

var arr = ['a','b','c','d']
arr.slice(1,arr.length).concat(arr.slice(0,1)

con mutación

var arr = ['a','b','c','d']
arr = arr.concat(arr.splice(0,1))
Haseeb A
fuente
1

Estoy compartiendo mi solución que estoy usando para girar en carrusel. Puede romperse cuando el tamaño de la matriz es menor que displayCount, pero puede agregar una condición adicional para dejar de rotar cuando es pequeña, o concatenando la matriz principal * displayCount veces también.

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]
emil
fuente
0

¿Qué tal aumentar un contador y luego obtener el resto de una división por la longitud de la matriz para llegar a donde se supone que debe estar?

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

Aparte de la sintaxis del lenguaje, esto debería funcionar bien.

tgandrews
fuente
2
Esto no rota el Array de ninguna manera.
Jean Vincent
1
Verdadero (como se indica en la respuesta), pero evita tener que rotar la matriz.
tgandrews
1
Pero el objetivo de esto ES rotar una matriz, según el título, no mostrar una matriz desde un cierto desplazamiento. Si usara el mismo bucle para reconstruir la nueva matriz, sería terriblemente más lento que otras versiones que usan métodos nativos de matriz. Además, olvidó salir del bucle infinito.
Jean Vincent
0

Si su matriz va a ser grande y / o va a rotar mucho, puede considerar usar una lista enlazada en lugar de una matriz.

Thomas Eding
fuente
De acuerdo, pero la pregunta se refiere a Arrays, y más específicamente a extender los verbos Array.
Jean Vincent
0

@molokoloco Necesitaba una función que pudiera configurar para rotar en una dirección: verdadero para adelante y falso para atrás. Creé un fragmento que toma una dirección, un contador y una matriz y genera un objeto con el contador incrementado en la dirección apropiada, así como los valores anterior, actual y siguiente. NO modifica la matriz original.

También lo comparé con tu fragmento y, aunque no es más rápido, es más rápido que los que comparas con el tuyo: un 21% más lento 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)
saranicole
fuente
Esto no rota la matriz. Para responder a esta pregunta, debe devolver una matriz donde el primer elemento está en el mostrador.
Jean Vincent
0

Llego tarde, pero tengo un ladrillo que agregar a estas buenas respuestas. Se me pidió que codificara dicha función y primero lo hice:

Array.prototype.rotate = function(n)
{
    for (var i = 0; i < n; i++)
    {
        this.push(this.shift());
    }
    return this;
}

Pero parecía ser menos eficiente que seguir cuando nes grande:

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

No estoy seguro de si esta es la forma más eficiente, pero me gusta la forma en que se lee, es lo suficientemente rápido para la mayoría de las tareas grandes, ya que lo he probado en producción ...

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

Andy González
fuente
0

Nativo, rápido, pequeño, semántico, funciona en motores antiguos y "curryable".

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

** Usando la última versión de JS podemos construirlo fácilmente **

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

aquí se mueve: número de rotaciones , una matriz que puede pasar un número aleatorio

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

Arrayen JS tiene un método incorporado a continuación que se puede usar para rotar una matriz con bastante facilidad y, obviamente, estos métodos son de naturaleza inmutable .

  • push: Inserta el elemento al final de la matriz.
  • pop: Elimina el elemento del final de la matriz.
  • unshift: Inserta el elemento al principio de la matriz.
  • shift: Elimina el elemento del principio de la matriz.

La siguiente solución ( ES6) toma dos argumentos, la matriz debe rotarse yn, el número de veces que la matriz debe rotarse.

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

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

Se puede agregar a Array.prototype y se puede utilizar en toda su aplicación

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]
Shushanth Pallegar
fuente
0

Utilizando for loop. Aquí están los pasos

  1. Almacene el primer elemento de la matriz como variable temporal.
  2. Luego cambia de izquierda a derecha.
  3. Luego asigne la variable temporal al último elemento de la matriz.
  4. Repita estos pasos para el número de rotaciones.

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

Maqsood Ahmed
fuente
0

con sintaxis es6

function rotLeft(a, d) {
    const removed = a.splice(0,d);
    return [...a, ...removed];
}
Sujoy Saha
fuente
si d> a.length, entonces no funcionará. Pruebe esto stackoverflow.com/a/63606959/569751
Aamir Afridi
-2

No estoy seguro de la eficiencia, pero lo haría de esta manera no mutante:

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

Mortec
fuente
Entonces, la primera parte de su respuesta "No estoy seguro acerca de la eficiencia" es como decir "No sé si esto responde a su pregunta". La pregunta original no pregunta "cuál es una buena manera de rotar una matriz", sino específicamente sobre la compacidad y la velocidad (memoria y eficiencia de tiempo, respectivamente). La pregunta tiene que ver con la eficiencia y no ha respondido a ese componente.
Richardpringle