Ruota gli elementi in un array in JavaScript


86

Mi chiedevo quale fosse il modo più efficiente per ruotare un array JavaScript.

Ho trovato questa soluzione, in cui un positivo nruota l'array a destra e un negativo na sinistra ( -length < n < length):

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

Che può quindi essere utilizzato in questo modo:

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

La mia versione originale sopra ha un difetto, come sottolineato da Christoph nei commenti qui sotto, una versione corretta è (il ritorno aggiuntivo consente il concatenamento):

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

Esiste una soluzione più compatta e / o più veloce, possibilmente nel contesto di un framework JavaScript? (nessuna delle versioni proposte di seguito è più compatta o più veloce)

Esiste un framework JavaScript con un array di rotazione integrato? (Ancora nessuna risposta da parte di nessuno)


1
Non capisco cosa dovrebbe fare il tuo esempio. Perché non usi solo months[new Date().getMonth()]per ottenere il nome del mese corrente?
Gumbo

1
@ Jean: il codice è rotto: il modo in cui lo fai, annulla lo spostamento degli elementi uniti come array e non individualmente; che dovrai utilizzare apply()per far funzionare la tua implementazione
Christoph

Oggi la rotazione modificherebbe i mesi per mostrare questo elenco (con Decin prima posizione):["Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"]
Jean Vincent

@Christoph, hai ragione, questo non funzionerebbe come una funzione di rotazione generica. Funziona solo se utilizzato per convertire in una stringa subito dopo.
Jean Vincent

@ Jean: puoi correggere la tua versione tramite Array.prototype.unshift.apply(this, this.splice(...))- la mia versione fa la stessa cosa, ma usa push()invece diunshift()
Christoph

Risposte:


62

Versione generica a prova di tipo che modifica l'array:

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

Nei commenti, Jean ha sollevato il problema che il codice non supporta il sovraccarico di push()e splice(). Non penso che questo sia davvero utile (vedi commenti), ma una soluzione rapida (un po 'un trucco, però) sarebbe quella di sostituire la riga

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

con questo:

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

Usare unshift()invece di push()è quasi due volte più veloce in Opera 10, mentre le differenze in FF erano trascurabili; il codice:

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
Bella memorizzazione nella cache dei Array.prototypemetodi! +1
James

Implementazione a prova di proiettile molto bella, ma in generale preferirei fare affidamento su eccezioni, o semplicemente risposte negative, per un cattivo utilizzo. Questo per mantenere il codice pulito e veloce. È responsabilità dell'utente trasmettere i parametri corretti o pagarne le conseguenze. Non mi piace la pena per i buoni utenti. A parte questo, questo è perfetto perché modifica l'array come richiesto e non soffre del difetto nella mia implementazione che ha annullato lo spostamento di un array invece dei singoli elementi come hai notato. Restituire questo è anche meglio per consentire il concatenamento. Quindi grazie.
Jean Vincent

Qual è il costo della chiusura qui, solo per cache push e unire?
Jean Vincent

@ Jean: beh, le chiusure catturano l'intera catena dell'ambito; fintanto che la funzione esterna è di primo livello, l'impatto dovrebbe essere trascurabile ed è comunque O (1), mentre cercare i metodi Array è O (n) nel numero di invocazioni; l'ottimizzazione delle implementazioni potrebbe integrare la ricerca, quindi non ci sarà alcun guadagno, ma con gli interpreti piuttosto stupidi con cui abbiamo avuto a che fare per molto tempo, la memorizzazione nella cache delle variabili in un ambito inferiore potrebbe avere un impatto significativo
Christoph

1
Questo non ruoterà gli array di grandi dimensioni. Ho controllato oggi e potrebbe gestire solo una serie di 250891 elementi di lunghezza. Apparentemente il numero di argomenti che puoi passare per applymetodo è limitato da una certa dimensione dello stack di chiamate. In termini ES6, anche l'operatore di spread soffrirebbe dello stesso problema di dimensione dello stack. Di seguito fornisco un metodo mappa per fare la stessa cosa. È più lento ma funziona per milioni di articoli. Puoi anche implementare la mappa con un semplice ciclo for o while e diventerà molto più veloce.
Redu

148

È possibile utilizzare push(), pop(), shift()e unshift()metodi:

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

utilizzo:

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

Se hai bisogno di countargomenti, vedi la mia altra risposta: https://stackoverflow.com/a/33451102 🖤🧡💚💙💜


Non ha un conteggio anche se non è un grosso problema a seconda dell'uso.
JustGage

@JustGage, pubblico un'altra risposta con il supporto di conteggio stackoverflow.com/questions/1985260/...
Yukulélé

2
Solo una nota: attenzione che questo metodo è invadente e manipolerà l'array inviato come parametro. Non è difficile risolvere la duplicazione dell'array prima di inviarlo
fguillen

Ecco una versione in dattiloscritto con variabile sinistra destra stackblitz.com/edit/typescript-vtcmhp
Dživo Jelić

38

Probabilmente farei qualcosa del genere:

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

Modifica     Ecco una versione mutator:

Array.prototype.rotate = function(n) {
    while (this.length && n < 0) n += this.length;
    this.push.apply(this, this.splice(0, n));
    return this;
}

tieni presente che questa funzione mantiene invariato l'array originale
Christoph

Deve modificare l'array originale (questo), proprio come fanno push, pop, shift, unshift, concat e splice. A parte questo, questo è valido.
Jean Vincent

@ Gumbo, perché il ciclo while? Non abbiamo bisogno di rendere n positivo, la giunzione funziona altrettanto bene con valori negativi. Alla fine, questa è giusta ma più o meno la versione di Christoph che ha capito bene senza il sovraccarico di avvertimento.
Jean Vincent

@ Gumbo, correzione sul mio commento precedente, i numeri negativi funzionano solo con la versione splice (n. This.length). Usare splice (0, n) come nella tua versione, richiede un int positivo.
Jean Vincent

1
Ho aggiunto n = n % this.lengthprima dell'istruzione return per gestire i numeri negativi e / o fuori limite.
iedmrc

25

Questa funzione funziona in entrambi i modi e funziona con qualsiasi numero (anche con un numero maggiore della lunghezza dell'array):

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

utilizzo:

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

risultato:

[ "🖤", "🧡", "💚", "💙", "💜" ]    -6
[ "🧡", "💚", "💙", "💜", "🖤" ]    -5
[ "💚", "💙", "💜", "🖤", "🧡" ]    -4
[ "💙", "💜", "🖤", "🧡", "💚" ]    -3
[ "💜", "🖤", "🧡", "💚", "💙" ]    -2
[ "🖤", "🧡", "💚", "💙", "💜" ]    -1
[ "🧡", "💚", "💙", "💜", "🖤" ]    0
[ "💚", "💙", "💜", "🖤", "🧡" ]    1
[ "💙", "💜", "🖤", "🧡", "💚" ]    2
[ "💜", "🖤", "🧡", "💚", "💙" ]    3
[ "🖤", "🧡", "💚", "💙", "💜" ]    4
[ "🧡", "💚", "💙", "💜", "🖤" ]    5
[ "💚", "💙", "💜", "🖤", "🧡" ]    6

Utilizzando questa funzione ho riscontrato un problema perché altera l'array originale. Ho trovato alcune soluzioni qui: stackoverflow.com/questions/14491405
ognockocaten

1
@ognockocaten Non è un problema, è come funziona questa funzione. se vuoi mantenere inalterato l'array originale, var arr2 = arrayRotate(arr.slice(0), 5)
clonalo

Questa è davvero un'ottima risposta e mi ha aiutato. Tuttavia, sarebbe meglio fornire un'alternativa che non muti l'array originale. Certo è facile creare semplicemente una copia dell'originale, questa non è la migliore pratica in quanto utilizza la memoria inutilmente.
Hybrid web dev

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

9

Molte di queste risposte sembrano troppo complicate e difficili da leggere. Non credo di aver visto nessuno usare giunzione 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;
}

Output di console.log (* generato a maggio):

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

Per quanto riguarda la compattezza, posso offrire un paio di funzioni generiche di una riga (senza contare la parte console.log | return). Basta inserire la matrice e il valore di destinazione negli argomenti.

Combino queste funzioni in una per un programma di gioco di carte a quattro giocatori in cui l'array è ['N', 'E', 'S', 'W']. Li ho lasciati separati nel caso qualcuno volesse copiare / incollare per le proprie esigenze. Per i miei scopi, utilizzo le funzioni quando cerco il turno successivo per giocare / agire durante le diverse fasi del gioco (Pinochle). Non mi sono preso la briga di testare la velocità, quindi se qualcun altro lo desidera, sentiti libero di farmi sapere i risultati.

* nota, l'unica differenza tra le funzioni è il "+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;
}

funzione di combinazione ...

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

adjustedArray =

W,N,E,S

1
Bella risposta. Era prima che passassi al lato oscuro (PHP)?
Nick

... Sono nato nel lato oscuro.
mickmackusa

:) btw l'ho usato qui
Nick

6

Usando la diffusione di ES6 per un esempio immutabile ...

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

e

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

Probabilmente non è il più efficiente, ma è conciso.


5

Soluzione facile con slice e destrutturazione:

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
Questo è davvero carino! È possibile evitare la necessità di eseguire il controllo count === 0impostando un valore predefinito. Questo ti permetterebbe di renderlo un one-liner:const rotate = (arr, n = 1) => [...arr.slice(n, arr.length), ...arr.slice(0, n)];
Nick F

4

Ecco un modo molto semplice per spostare gli elementi in un array:

function rotate(array, stepsToShift) {

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

    return array;
}

3

@Christoph, hai fatto un codice pulito, ma il 60% più lento di questo che ho trovato. Guarda il risultato su jsPerf: http://jsperf.com/js-rotate-array/2 [Modifica] OK ora ci sono più browser e non sono ovvi metodi stregati i migliori

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 Intuitivamente la tua soluzione sarebbe più lenta poiché l'incremento sarebbe maggiore perché stai usando un ciclo. Ho aggiornato il tuo test case con un incremento di 5 e sembra quindi funzionare più lentamente: jsperf.com/js-rotate-array/3
Jean Vincent

Non capisco cosa stia facendo la funzione rotateArray () ^^ solo che funziona :) (è un codice strano!) Chrome si comporta quasi l'opposto di Firefox ...
molokoloco

Questo metodo è molto interessante, funziona scambiando i riferimenti agli elementi nell'array. La dimensione dell'array non cambia mai, motivo per cui è molto veloce con l'inconveniente di utilizzare i loop. La cosa interessante che mostra è che Firefox è il più veloce nei loop mentre chrome è il più veloce nelle manipolazioni degli array.
Jean Vincent

1
Dopo aver analizzato il codice, ho trovato un punto debole che mostra che questa soluzione è più veloce solo per array più piccoli e con determinati conteggi di rotazione: jsperf.com/js-rotate-array/5 Tra tutti i browser il più veloce rimane Google Chrome con splice / unshift soluzione. Ciò significa che questa è davvero una questione di ottimizzazione del metodo Array che Chrome fa meglio degli altri browser.
Jean Vincent

3

vedere 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

Quando non sono riuscito a trovare uno snippet già pronto per iniziare un elenco di giorni con "oggi", l'ho fatto in questo modo (non del tutto generico, probabilmente molto meno raffinato degli esempi precedenti, ma ha fatto il lavoro):

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

Grazie a un piccolo refactoring di un programmatore migliore di me è una riga o due più corta del mio tentativo iniziale, ma qualsiasi ulteriore commento sull'efficienza è il benvenuto.


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

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

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

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

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

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

In una riga di codice:

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

2

La risposta accettata ha un difetto di non essere in grado di gestire array più grandi della dimensione dello stack di chiamate che dipende dalla sessione ma dovrebbe essere di circa 100 ~ 300.000 elementi. Ad esempio, nell'attuale sessione di Chrome che ho provato era 250891. In molti casi potresti non sapere nemmeno in quale dimensione l'array potrebbe crescere dinamicamente. Quindi questo è un problema serio.

Per superare questa limitazione, immagino che un metodo interessante sia utilizzare Array.prototype.map()e mappare gli elementi riorganizzando gli indici in modo circolare. Questo metodo accetta un argomento intero. Se questo argomento è positivo ruoterà sugli indici crescenti e se negativo sulla direzione degli indici decrescenti. Questo ha solo una complessità di tempo O (n) e restituirà un nuovo array senza modificare quello su cui è chiamato mentre gestisce milioni di elementi senza alcun problema. Vediamo come funziona;

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

In realtà dopo che sono stato criticato su due questioni come segue;

  1. Non è necessario un condizionale per input positivo o negativo poiché rivela una violazione di DRY. Potresti farlo con una mappa perché ogni n negativo ha un equivalente positivo (Totalmente giusto ..)
  2. Una funzione Array dovrebbe cambiare l'array corrente o crearne uno nuovo, la tua funzione potrebbe fare a seconda che uno spostamento sia necessario o meno (Totalmente giusto ..)

Ho deciso di modificare il codice come segue;

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

Poi di nuovo; ovviamente i funtori JS come Array.prototype.map()sono lenti rispetto ai loro equivalenti codificati in semplice JS. Al fine di ottenere un aumento delle prestazioni superiore al 100%, la seguente sarebbe probabilmente la mia scelta Array.prototype.rotate()se dovessi mai ruotare un array nel codice di produzione come quello che ho usato nel mio tentativo suString.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 () e splice (), per quanto lenti possano essere, sono in realtà più veloci rispetto all'utilizzo di map () o di qualsiasi metodo che utilizza un parametro di funzione. shift () e splice () essendo più dichiarativi sono anche più facili da ottimizzare a lungo termine.
Jean Vincent

@ Jean Vincent Sì, hai ragione .. la mappa in effetti potrebbe rivelarsi più lenta, ma credo che la logica di rotazione ideale sia questa. Ho solo provato a mostrare l'approccio logico. La mappa può essere facilmente semplificata con un ciclo for e risulterebbe un significativo miglioramento della velocità ... Tuttavia la cosa reale è che la risposta accettata ha un approccio sviluppato con alcuni altri linguaggi in mente. Non è l'ideale per JS ... non verrà nemmeno eseguito quando la dimensione dell'array è maggiore della dimensione dello stack di chiamate. (nel mio caso e in questa sessione si è rivelato essere limitato a soli 250891 articoli) Quindi c'è quello ..!
Redu

Hai ragione sulla dimensione dello stack di chiamate che può essere applicata, questo è un argomento più valido della velocità, considera la possibilità di modificare la tua risposta per enfatizzare questo punto con un rientro migliorato.
Jean Vincent

2

Questa funzione è un po 'più veloce della risposta accettata per piccoli array ma MOLTO più veloce per grandi array. Questa funzione consente anche un numero arbitrario di rotazioni maggiore della lunghezza dell'array, che è una limitazione della funzione originale.

Infine, la risposta accettata ruota nella direzione opposta come descritto.

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 l'equivalente funzionale (che sembra avere anche alcuni vantaggi in termini di prestazioni):

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

Puoi controllare la ripartizione delle prestazioni qui.


Sfortunatamente non funziona, riduce l'array a dimensione zero, il che spiega perché è molto più veloce, dopo la prima esecuzione, rispetto a qualsiasi altra implementazione corretta, specialmente su array più grandi. Nel tuo test di benchmark se cambi l'ordine dei test e visualizzi a.length alla fine, vedrai il problema.
Jean Vincent

2

EDIT :: Hey quindi risulta che ci sono troppe iterazioni in corso. Nessun loop, nessuna diramazione.

Funziona ancora con n negativo per rotazione a destra e n positivo per rotazione a sinistra per qualsiasi dimensione n, senza mutazione

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

Ecco la versione da golf in codice per le risatine

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

EDIT1 :: * Implementazione senza rami e senza mutazioni.

Quindi hey, risulta che avevo un ramo in cui non ne avevo bisogno. Ecco una soluzione funzionante. num negativo = ruota a destra di | num | num positivo = rotazione a sinistra di num

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

L'equazione ((n%l) + l) % lmappa esattamente numeri positivi e negativi di qualsiasi valore arbitrariamente grande di n

ORIGINALE

Ruota a sinistra ea destra. Ruota a sinistra con il positivo n, ruota a destra con il negativo n.

Funziona per input oscenamente grandi di n.

Nessuna modalità di mutazione. Troppa mutazione in queste risposte.

Inoltre, meno operazioni rispetto alla maggior parte delle risposte. Nessun pop, nessuna spinta, nessuna giunzione, nessuno spostamento.

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

Spiegazione:

mappare ogni indice di A al valore all'offset dell'indice. In questo caso

offset = num

se offset < 0allora offset + index + positive length of Apunterà all'offset inverso.

se offset > 0 and offset < length of Aquindi mappare semplicemente l'indice corrente all'indice di offset di A.

Altrimenti, modulo l'offset e la lunghezza per mappare l'offset nei limiti dell'array.

Prendiamo ad esempio offset = 4e offset = -4.

Quando offset = -4, e A = [1,2,3,4,5], per ogni indice, offset + indexridurranno la grandezza (o Math.abs(offset)).

Spieghiamo prima il calcolo per l'indice di n negativo. A[(((n % A.length) + A.length) % A.length)+0]e sono stato intimidito. Non esserlo. Mi ci sono voluti 3 minuti in una replica per risolverlo.

  1. Sappiamo che nè negativo perché il caso lo è n < 0. Se il numero è maggiore dell'intervallo dell'array, n % A.lengthlo mapperà nell'intervallo.
  2. n + A.lengthaggiungere quel numero a A.lengthper compensare n l'importo corretto.
  3. Sappiamo che nè negativo perché il caso lo è n < 0. n + A.lengthaggiungere quel numero a A.lengthper compensare n l'importo corretto.
  4. Avanti Mappalo all'intervallo della lunghezza di A usando modulo. Il secondo modulo è necessario per mappare il risultato del calcolo in un intervallo indicizzabile

    inserisci qui la descrizione dell'immagine

  5. Primo indice: -4 + 0 = -4. A.length = 5. A.length - 4 = 1. A 2 è 2. Indice mappa da 0 a 2.[2,... ]

  6. Indice successivo, -4 + 1 = -3. 5 + -3 = 2. A 2 è 3. Indice mappa da 1 a 3.[2,3... ]
  7. Eccetera.

Lo stesso processo si applica a offset = 4. Quando offset = -4e A = [1,2,3,4,5], per ogni indice, offset + indexaumenteranno la grandezza.

  1. 4 + 0 = 0. Associare A [0] al valore in A [4].[5...]
  2. 4 + 1 = 5, 5 è fuori dai limiti durante l'indicizzazione, quindi mappare A 2 al valore al resto di 5 / 5, che è 0. A 2 = valore in A [0].[5,1...]
  3. ripetere.

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

Soluzione non mutante

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

con mutazione

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

1

Condivido la mia soluzione che sto usando per ruotare sul carosello. Potrebbe interrompersi quando la dimensione dell'array è inferiore a displayCount, ma è possibile aggiungere una condizione aggiuntiva per interrompere la rotazione quando è piccola o concatenare anche l'array principale * displayCount volte.

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

Che ne dici di incrementare un contatore e quindi ottenere il resto di una divisione in base alla lunghezza dell'array per arrivare dove dovresti essere.

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

A parte la sintassi del linguaggio, questo dovrebbe funzionare bene.


2
Ciò non ruota in alcun modo l'array.
Jean Vincent

1
Vero (come indicato nella risposta), ma evita di dover ruotare l'array.
tgandrews

1
Ma il punto centrale di questo è ruotare un array, come da titolo, non visualizzare un array da un certo offset. Se utilizzassi lo stesso ciclo per ricostruire il nuovo array, sarebbe terribilmente più lento di altre versioni che utilizzano metodi Array nativi. Inoltre ti sei dimenticato di uscire dal loop infinito.
Jean Vincent

0

Se il tuo array sarà grande e / o ruoterai molto, potresti prendere in considerazione l'utilizzo di un elenco collegato invece di un array.


D'accordo, ma la domanda riguarda gli array e più specificamente l'estensione dei verbi Array.
Jean Vincent

0

@molokoloco Avevo bisogno di una funzione che potessi configurare per ruotare in una direzione: vero per avanti e falso per indietro. Ho creato uno snippet che prende una direzione, un contatore e un array e genera un oggetto con il contatore incrementato nella direzione appropriata, nonché i valori precedenti, correnti e successivi. NON modifica l'array originale.

L'ho anche confrontato con il tuo snippet e sebbene non sia più veloce, è più veloce di quelli con cui confronti il ​​tuo - 21% più 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)

Questo non ruota l'array. Per rispondere a questa domanda, è necessario restituire un array in cui il primo elemento è al banco.
Jean Vincent

0

Arrivo in ritardo ma ho un mattone da aggiungere a queste buone risposte. Mi è stato chiesto di codificare una tale funzione e per prima cosa ho fatto:

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

Ma sembrava essere meno efficiente di quanto segue 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]
    });
}

0

Non sono sicuro che questo sia il modo più efficiente, ma mi piace il modo in cui legge, è abbastanza veloce per le attività più grandi poiché l'ho testato in produzione ...

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

Nativo, veloce, piccolo, semantico, lavora su vecchi motori e "curryable".

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

0

** Utilizzando l'ultima versione di JS possiamo costruirla facilmente **

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

qui si muove: numero di rotazioni , un array che puoi passare numero casuale

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

0

Array in JS ha un metodo integrato di seguito che può essere utilizzato per ruotare un array abbastanza facilmente e ovviamente questi metodi lo sono di natura immutabile .

  • push: Inserisce l'elemento alla fine della matrice.
  • pop: Rimuove l'elemento dalla fine dell'array.
  • unshift: Inserisce l'elemento all'inizio della matrice.
  • shift: Rimuove l'elemento dall'inizio dell'array.

La seguente soluzione ( ES6) accetta due argomenti, l'array deve essere ruotato en, il numero di volte che l'array deve essere ruotato.

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

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

Può essere aggiunto a Array.prototype e può essere utilizzato in tutta l'applicazione

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

Utilizzo del ciclo for. Ecco i passaggi

  1. Memorizza il primo elemento dell'array come variabile temporanea.
  2. Quindi scambia da sinistra a destra.
  3. Quindi assegna la variabile temporanea all'ultimo elemento dell'array.
  4. Ripeti questi passaggi per il numero di rotazioni.

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



0

Utilizza il seguente -

arr=[1,2,3,4,5]  
let arrs=[]
arrs=arr.slice(d%arr.length).concat(arr.slice(0,d%arr.length))

Dove, il dnumero di rotazioni è la
migliore soluzione, non è necessario applicare le tecniche di forza bruta di Popping Pushing con complessità temporale O (1)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.