Gire os elementos em uma matriz em JavaScript

86

Eu estava me perguntando qual era a maneira mais eficiente de girar um array JavaScript.

Eu descobri esta solução, onde um positivo ngira a matriz para a direita e um negativo npara a esquerda ( -length < n < length):

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

Que pode então ser usado desta forma:

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

Minha versão original acima tem uma falha, como apontado por Christoph nos comentários abaixo, uma versão correta é (o retorno adicional permite encadeamento):

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

Existe uma solução mais compacta e / ou rápida, possivelmente no contexto de um framework JavaScript? (nenhuma das versões propostas abaixo é mais compacta ou mais rápida)

Existe alguma estrutura de JavaScript com uma rotação de array embutida? (Ainda não respondido por ninguém)

Jean Vincent
fonte
1
Não entendo o que seu exemplo deve fazer. Por que você não usa apenas months[new Date().getMonth()]para obter o nome do mês atual?
Gumbo
1
@Jean: o código está quebrado: da maneira que você faz, ele não mudará os elementos emendados como um array e não individualmente; você terá que usar apply()para fazer sua implementação funcionar
Christoph
Hoje a rotação modificaria meses para mostrar esta lista (com Decna primeira posição):["Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"]
Jean Vincent
@Christoph, você está certo, isso não funcionaria como uma função de rotação genérica. Ele só funciona se for usado para converter para uma string logo em seguida.
Jean Vincent
@Jean: você pode corrigir sua versão via Array.prototype.unshift.apply(this, this.splice(...))- minha versão faz a mesma coisa, mas usa em push()vez deunshift()
Christoph

Respostas:

60

Versão genérica com segurança de tipo que altera a 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;
    };
})();

Nos comentários, Jean levantou a questão de que o código não suporta sobrecarga de push()e splice(). Não acho que isso seja realmente útil (veja os comentários), mas uma solução rápida (um pouco como um hack, embora) seria substituir a linha

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

com este:

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

Usar em unshift()vez de push()é quase duas vezes mais rápido no Opera 10, enquanto as diferenças no FF eram insignificantes; o 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
fonte
2
Bom cache de Array.prototypemétodos! +1
James
Implementação à prova de bala muito boa, mas em geral eu preferiria contar com exceções, ou simplesmente respostas ruins, para mau uso. Isso para manter o código limpo e rápido. É responsabilidade do usuário passar os parâmetros corretos ou pagar as consequências. Não gosto da penalidade para bons usuários. Fora isso, isso é perfeito porque modifica o Array conforme solicitado e não sofre com a falha em minha implementação, que removeu o deslocamento de um array em vez dos elementos individuais, como você observou. Retornar isso também é melhor para permitir o encadeamento. Então, obrigado.
Jean Vincent
Qual é o custo do fechamento aqui, apenas para fazer o cache push e emendar?
Jean Vincent
@Jean: bem, os fechamentos capturam toda a cadeia de escopo; contanto que a função externa seja de nível superior, o impacto deve ser insignificante e é O (1) de qualquer maneira, ao passo que pesquisar os métodos Array é O (n) no número de invocações; otimizar implementações pode embutir a pesquisa para que não haja nenhum ganho, mas com os intérpretes um tanto estúpidos com os quais tivemos que lidar por muito tempo, o cache de variáveis ​​em um escopo inferior poderia ter um impacto significativo
Christoph
1
Isso não girará matrizes grandes. Eu verifiquei hoje e ele só podia lidar com uma matriz de 250891 itens de comprimento. Aparentemente, o número de argumentos que você pode passar pelo applymétodo é limitado por um determinado tamanho de pilha de chamadas. Em termos de ES6, o operador de spread também sofreria do mesmo problema de tamanho de pilha. Abaixo, apresento um método de mapa para fazer a mesma coisa. É mais lento, mas funciona para milhões de itens. Você também pode implementar o mapa com um loop for ou while simples e ele ficará muito mais rápido.
Redu de
144

Você pode usar push(), pop(), shift()e 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'];

Se você precisar de um countargumento, veja minha outra resposta: https://stackoverflow.com/a/33451102 🖤🧡💚💙💜

Yukulélé
fonte
Não tem uma contagem, embora não seja um grande negócio, dependendo do uso.
JustGage
@JustGage, publico outra resposta com suporte a contagem stackoverflow.com/questions/1985260/…
Yukulélé
2
Apenas uma observação: cuidado, pois este método é intrusivo e irá manipular o array enviado como parâmetro. Não é difícil resolver duplicar a matriz antes de enviá-la
fguillen
Aqui está uma versão datilografada com a variável esquerda direita stackblitz.com/edit/typescript-vtcmhp
Dživo Jelić
38

Eu provavelmente faria algo assim:

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

Editar     Aqui está uma versão modificadora:

Array.prototype.rotate = function(n) {
    while (this.length && n < 0) n += this.length;
    this.push.apply(this, this.splice(0, n));
    return this;
}
quiabo
fonte
tenha em mente que esta função mantém a matriz original inalterada
Christoph
Ele precisa modificar o Array original (this), assim como push, pop, shift, unshift, concat e splice. Fora isso, isso é válido.
Jean Vincent
@Gumbo, por que o loop while? Não precisamos tornar n positivo, o splice também funciona com valores negativos. Para o final, esta é a versão certa, mas praticamente a de Christoph que acertou primeiro, sem a ressalva de sobrecarga.
Jean Vincent
@Gumbo, correção do meu comentário anterior, os números negativos funcionam apenas na versão splice (n. This.length). Usar splice (0, n) como em sua versão requer um int positivo.
Jean Vincent
1
Eu adicionei n = n % this.lengthantes da instrução return para lidar com números negativos e / ou fora do limite.
iedmrc
25

Esta função funciona em ambos os sentidos e funciona com qualquer número (mesmo com um número maior que o comprimento da 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é
fonte
Usando essa função, tive um problema porque ela altera a matriz original. Encontrei algumas soluções alternativas aqui: stackoverflow.com/questions/14491405
ognockocaten
1
@ognockocaten Não é um problema, é como essa função funciona. se você quiser manter a matriz original inalterada, clone-a antes:var arr2 = arrayRotate(arr.slice(0), 5)
Yukulélé
Esta é realmente uma ótima resposta e me ajudou. No entanto, seria melhor fornecer uma alternativa que não modifique o array original. Claro que é fácil simplesmente criar uma cópia do original, essa não é a prática recomendada, pois usa memória desnecessariamente.
Hybrid web dev
@Hybridwebdev useconst immutatableArrayRotate = (arr, count) => ArrayRotate(arr.clone(), count)
Yukulélé
8

Muitas dessas respostas parecem complicadas e difíceis de ler. Acho que não vi ninguém usando splice com 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;
}

Saídas console.log (* geradas em maio):

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

Quanto à compactação, posso oferecer algumas funções genéricas de uma linha (sem contar a parte console.log | return). Basta inserir a matriz e o valor alvo nos argumentos.

Eu combino essas funções em uma para um programa de jogo de cartas para quatro jogadores, onde a matriz é ['N', 'E', 'S', 'W']. Deixei-os separados caso alguém queira copiar / colar para suas necessidades. Para os meus propósitos, utilizo as funções quando procuro quem é a próxima vez de jogar / agir durante as diferentes fases do jogo (Pinochle). Não me preocupei em testar a velocidade, então, se outra pessoa quiser, fique à vontade para me informar os resultados.

* Observe, a única diferença entre as funções é o "+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;
}

função de combinação ...

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
Mickmackusa
fonte
1
Boa resposta. Foi antes de você ir para o lado negro (PHP)?
Nick
... Eu nasci no lado escuro.
mickmackusa
:) btw eu usei aqui
Nick
6

Usando a propagação do ES6 como um exemplo imutável ...

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

e

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

Provavelmente não é o mais eficiente, mas é conciso.

Dudley Craig
fonte
5

Solução fácil com fatia e desestruturação:

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
fonte
2
Isso é muito bom! Você pode evitar a necessidade de verificação count === 0definindo um valor padrão. Isso permitiria que você fizesse um one-liner:const rotate = (arr, n = 1) => [...arr.slice(n, arr.length), ...arr.slice(0, n)];
Nick F
@NickF legal, obrigado! Não pensei dessa forma.
kashesandr
4

Esta é uma maneira muito simples de deslocar itens em uma matriz:

function rotate(array, stepsToShift) {

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

    return array;
}
aryeh
fonte
3

@Christoph, você fez um código limpo, mas 60% mais lento do que este que encontrei. Veja o resultado em jsPerf: http://jsperf.com/js-rotate-array/2 [Editar] OK, agora há mais navegadores e métodos não óbvios de bruxa, os melhores

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
fonte
@molokocolo Intuitivamente, sua solução seria executada mais lentamente, pois o incremento seria maior porque você está usando um loop. Eu atualizei seu caso de teste com um incremento de 5 e ele parece ficar mais lento: jsperf.com/js-rotate-array/3
Jean Vincent
Não imagino o que está fazendo a função rotateArray () ^^ apenas que funciona :) (é um código estranho!) O Chrome se comporta quase o oposto do Firefox ...
molokoloco
Este método é muito interessante, ele funciona trocando referências de elementos no array. O tamanho do array nunca muda, por isso é muito rápido, com a desvantagem de usar loops. O interessante que mostra é que o Firefox é o mais rápido em loops, enquanto o Chrome é o mais rápido em manipulações de matrizes.
Jean Vincent
1
Depois de analisar o código, descobri uma fraqueza que mostra que esta solução só é mais rápida para arrays menores e certas contagens de rotação: jsperf.com/js-rotate-array/5 Entre todos os navegadores, o mais rápido continua sendo o Google Chrome com splice / unshift solução. Isso significa que essa é realmente uma questão de otimização do método Array que o Chrome faz melhor do que os outros navegadores.
Jean Vincent
3

consulte 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
fonte
3

Quando não consegui encontrar um snippet pronto para começar uma lista de dias com 'hoje', fiz assim (não muito genérico, provavelmente muito menos refinado do que os exemplos acima, mas deu certo):

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

Graças a uma pequena refatoração por um programador melhor do que eu, é uma ou duas linhas mais curta do que minha tentativa inicial, mas quaisquer comentários adicionais sobre eficiência são bem-vindos.

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

Em uma linha de código:

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

A resposta aceita tem uma falha de não ser capaz de lidar com matrizes maiores do que o tamanho da pilha de chamadas, que depende da sessão, mas deve ter cerca de 100 ~ 300 mil itens. Por exemplo, na sessão atual do Chrome que experimentei, era 250891. Em muitos casos, você pode nem saber o tamanho do array pode crescer dinamicamente. Então esse é um problema sério.

Para superar essa limitação, acho que um método interessante é utilizar Array.prototype.map()e mapear os elementos reorganizando os índices de maneira circular. Este método aceita um argumento inteiro. Se este argumento for positivo, ele girará na direção dos índices crescentes e se negativo na direção dos índices decrescentes. Isso tem apenas complexidade de tempo O (n) e retornará um novo array sem alterar aquele que é chamado enquanto manipula milhões de itens sem nenhum problema. Vamos ver como 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));

Na verdade, depois de ter sido criticado em dois assuntos como segue;

  1. Não há necessidade de uma condicional para entrada positiva ou negativa, uma vez que revela uma violação de DRY. Você poderia fazer isso com um mapa porque cada n negativo tem um equivalente positivo (totalmente certo ..)
  2. Uma função Array deve alterar o array atual ou fazer um novo array, sua função pode fazer tanto dependendo se uma mudança é necessária ou não (Totalmente correto ..)

Decidi modificar o código da seguinte maneira;

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

Então de novo; é claro que os functores JS Array.prototype.map()são lentos em comparação com seus equivalentes codificados em JS simples. A fim de obter mais de 100% de aumento de desempenho, o seguinte provavelmente seria minha escolha Array.prototype.rotate()se algum dia eu precisasse girar um array no código de produção como o que usei em minha tentativa 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
fonte
shift () e splice (), por mais lentos que sejam, são na verdade mais rápidos do que usar map () ou qualquer método que use um parâmetro de função. shift () e splice () sendo mais declarativos, também são mais fáceis de otimizar a longo prazo.
Jean Vincent
@Jean Vincent Sim, você está certo ... o mapa na verdade pode acabar sendo mais lento, mas acredito que a lógica de rotação ideal é essa. Eu apenas tentei mostrar a abordagem lógica. O mapa pode ser facilmente simplificado com um loop for e resultaria em uma melhora significativa na velocidade ... No entanto, a verdade é que a resposta aceita tem uma abordagem desenvolvida com algumas outras linguagens em mente. Não é ideal para JS ... nem mesmo será executado quando o tamanho do array for maior que o tamanho da pilha de chamadas. (no meu caso e nesta sessão acabou por ficar limitado a apenas 250891 itens) Então é isso ..!
Redu de
Você está certo sobre o tamanho da pilha de chamadas que se aplica pode exceder, este é um argumento mais válido do que a velocidade, considere editar sua resposta para enfatizar este ponto com recuo aprimorado também.
Jean Vincent de
2

Esta função é um pouco mais rápida do que a resposta aceita para pequenos arrays, mas MUITO mais rápida para grandes arrays. Essa função também permite um número arbitrário de rotações maior do que o comprimento da matriz, o que é uma limitação da função original.

Por último, a resposta aceita gira na direção oposta, conforme descrito.

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

E o equivalente funcional (que parece também ter alguns benefícios de desempenho):

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

Você pode verificar a análise de desempenho aqui.

Tanner Stults
fonte
Infelizmente, isso não funciona, ele reduz o array para o tamanho zero, o que explica porque é muito mais rápido, após a primeira execução, do que qualquer outra implementação correta, especialmente em arrays maiores. Em seu teste de benchmark, se você trocar a ordem dos testes e exibir a.length no final, verá o problema.
Jean Vincent
2

EDITAR:: Ei, acontece que há muita iteração acontecendo. Sem loops, sem ramificação.

Ainda funciona com n negativo para rotação à direita e n positivo para rotação à esquerda para qualquer tamanho n, sem mutação

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

Aqui está a versão do código de golfe para risos

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

EDIT1 :: * Implementação sem ramificações e sem mutação.

Então, ei, descobri que eu tinha um galho que não precisava. Aqui está uma solução de trabalho. num negativo = girar para a direita por | num | num positivo = girar para a esquerda por num

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

A equação ((n%l) + l) % lmapeia números exatamente positivos e negativos de quaisquer valores arbitrariamente grandes de n

ORIGINAL

Gire para a esquerda e para a direita. Gire para a esquerda com positivo n, gire para a direita com negativo n.

Funciona para entradas obscenamente grandes de n.

Sem modo de mutação. Muita mutação nessas respostas.

Além disso, menos operações do que a maioria das respostas. Sem estalo, sem empurrar, sem emenda, sem mudança.

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

ou

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

Explicação:

mapeie cada índice de A para o valor no deslocamento do índice. Nesse caso

offset = num

se o offset < 0then offset + index + positive length of Airá apontar para o deslocamento inverso.

se, offset > 0 and offset < length of Aentão, simplesmente mapeie o índice atual para o índice de deslocamento de A.

Caso contrário, module o deslocamento e o comprimento para mapear o deslocamento nos limites da matriz.

Tomemos por exemplo offset = 4e offset = -4.

Quando offset = -4, e A = [1,2,3,4,5], para cada índice, offset + indextornará a magnitude (ou Math.abs(offset)) menor.

Vamos explicar o cálculo do índice de n negativo primeiro. A[(((n % A.length) + A.length) % A.length)+0]e foi intimidado. Não sinta. Levei 3 minutos em um Repl para resolver isso.

  1. Sabemos que né negativo porque o caso é n < 0. Se o número for maior do que o intervalo do Array, n % A.lengthirá mapeá-lo para o intervalo.
  2. n + A.lengthadicione esse número para A.lengthcompensar na quantidade correta.
  3. Sabemos que né negativo porque o caso é n < 0. n + A.lengthadicione esse número aA.length compensar na quantidade correta.
  4. Próximo Mapeie-o para a faixa de comprimento de A usando módulo. O segundo módulo é necessário para mapear o resultado do cálculo em uma faixa indexável

    insira a descrição da imagem aqui

  5. Primeiro índice: -4 + 0 = -4. A.length = 5. a.length - 4 = 1. A 2 é 2. Mapa índice de 0 a 2.[2,... ]

  6. Próximo índice, -4 + 1 = -3. 5 + -3 = 2. A 2 é 3. Índice do mapa 1 a 3.[2,3... ]
  7. Etc.

O mesmo processo se aplica a offset = 4. Quando offset = -4, e A = [1,2,3,4,5], para cada índice, offset + indexaumentará a magnitude.

  1. 4 + 0 = 0. Mapeie A [0] para o valor em A [4].[5...]
  2. 4 + 1 = 5, 5 está fora dos limites durante a indexação, então mapeie A 2 para o valor no restante de 5 / 5, que é 0. A 2 = valor em A [0].[5,1...]
  3. repetir.
nathan rogers
fonte
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
fonte
1

Solução não mutante

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

com mutação

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

Estou compartilhando minha solução que estou usando para girar no carrossel. Ele pode quebrar quando o tamanho do array for menor do que displayCount, mas você pode adicionar uma condição extra para parar de girar quando for pequeno ou concatenar o array principal * displayCount vezes também.

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
fonte
0

Que tal incrementar um contador e então obter o resto de uma divisão pelo comprimento do array para chegar onde você deveria estar.

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

Deixando de lado a sintaxe da linguagem, isso deve funcionar bem.

tgandrews
fonte
2
Isso não gira o Array de forma alguma.
Jean Vincent
1
Verdadeiro (conforme declarado na resposta), mas evita a necessidade de girar a matriz.
tgandrews 01 de
1
Mas o objetivo disso é girar um array, de acordo com o título, não exibir um array a partir de um certo deslocamento. Se você usasse o mesmo loop para reconstruir o novo array, seria terrivelmente mais lento do que outras versões que usam métodos de Array nativos. Além disso, você se esqueceu de sair do loop infinito.
Jean Vincent
0

Se seu array vai ser grande e / ou você vai girar muito, você pode querer considerar o uso de uma lista encadeada em vez de um array.

Thomas Eding
fonte
Concordo, mas a questão pertence a Arrays e, mais especificamente, a estender verbos de Array.
Jean Vincent
0

@molokoloco Eu precisava de uma função que pudesse configurar para girar em uma direção - verdadeiro para frente e falso para trás. Eu criei um snippet que leva uma direção, um contador e uma matriz e produz um objeto com o contador incrementado na direção apropriada, bem como os valores anteriores, atuais e próximos. Ele NÃO modifica o array original.

Eu também o comparei com o seu snippet e, embora não seja mais rápido, é mais rápido do que aqueles com os quais você compara o seu - 21% mais 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
fonte
Isso não gira o Array. Para responder a esta pergunta, você precisa retornar um Array onde o primeiro item está no balcão.
Jean Vincent de
0

Estou chegando tarde, mas tenho um tijolo a acrescentar a essas boas respostas. Pediram-me para codificar essa função e primeiro fiz:

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

Mas parecia menos eficiente do que seguir quando né 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
fonte
0

Não tenho certeza se esta é a forma mais eficiente, mas gosto da forma como lê, é rápido o suficiente para a maioria das tarefas grandes, pois testei em produção ...

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 Gonzalez
fonte
0

Nativo, rápido, pequeno, semântico, funciona em motores antigos e "curryable".

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

** Usando a versão mais recente do JS, podemos construí-lo facilmente **

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

aqui se move: número de rotações , um array que você pode passar por número aleatório

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

Arrayem JS tem o método embutido abaixo que pode ser usado para girar uma matriz com bastante facilidade e, obviamente, esses métodos são imutáveis por natureza.

  • push: Insere o item no final da matriz.
  • pop: Remove o item do final da matriz.
  • unshift: Insere o item no início da matriz.
  • shift: Remove o item do início da matriz.

A solução abaixo ( ES6) leva dois argumentos, a matriz precisa ser girada en, o número de vezes que a matriz deve ser girada.

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

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

Ele pode ser adicionado a Array.prototype e pode ser usado em todo o seu aplicativo

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
fonte
0

Usando o loop for. Aqui estão os passos

  1. Armazene o primeiro elemento da matriz como variável temporária.
  2. Em seguida, troque da esquerda para a direita.
  3. Em seguida, atribua a variável temporária ao último elemento da matriz.
  4. Repita essas etapas para o número de rotações.

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
fonte
0

com sintaxe es6

function rotLeft(a, d) {
    const removed = a.splice(0,d);
    return [...a, ...removed];
}
Sujoy Saha
fonte
se d> a.length então não funcionará. Experimente stackoverflow.com/a/63606959/569751
Aamir Afridi
-2

Não tenho certeza sobre a eficiência, mas faria desta forma não mutante:

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

Mortec
fonte
Portanto, a primeira parte da sua resposta "Não tenho certeza sobre a eficiência" é como dizer "Não sei se isso responde a sua pergunta". A pergunta original não pergunta "qual é uma boa maneira de girar uma matriz", ela pergunta especificamente sobre compactação e velocidade (eficiência de memória e tempo, respectivamente). A questão é toda sobre eficiência e você não respondeu a esse componente.
richardpringle