Rotation du tableau JavaScript()

Je me demandais quel était le moyen le plus efficace de faire pivoter un tableau JavaScript.

J'ai trouvé cette solution, où un n positif fait pivoter le tableau vers la droite, et un n négatif vers la gauche (-length < n < length) :

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

Qui peut alors être utilisé de cette façon:

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

Ma version originale ci-dessus a un défaut, comme indiqué par Christoph dans les commentaires ci-dessous, une version correcte est (le retour supplémentaire permet le chaînage):

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

Y a-t-il un solution plus compacte et / ou plus rapide, éventuellement dans le contexte D'un framework JavaScript? (aucune des versions proposées ci-dessous n'est plus compacte ou plus rapide)

Existe-t-il un framework JavaScript avec une rotation de tableau intégrée? (Toujours pas répondu par personne)

52
demandé sur Victor 2009-12-31 15:44:10

17 réponses

Type-safe, version générique qui mute le tableau:

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

Dans les commentaires, Jean a soulevé le problème que le code ne supporte pas la surcharge de push() et splice(). Je ne pense pas que ce soit vraiment utile (voir les commentaires), mais une solution rapide (un peu un hack, cependant) serait de remplacer la ligne

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

Avec celui-ci:

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

Utiliser unshift() au lieu de push() est presque deux fois plus rapide dans Opera 10, alors que les différences dans FF étaient négligeables; le code:

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;
    };
})();
43
répondu Christoph 2015-02-07 19:59:46

EDIT: Voir aussi ma réponse avec count argument: https://stackoverflow.com/a/33451102

Vous pouvez utiliser push(), pop(), shift() et unshift() fonctions:

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

Utilisation:

arrayRotate(['h','e','l','l','o']);       // ['e','l','l','o','h'];
arrayRotate(['h','e','l','l','o'], true); // ['o','h','e','l','l'];
89
répondu Yukulélé 2018-04-10 14:26:18

Je ferais probablement quelque chose comme ceci:

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

Edit Voici un mutateur version:

Array.prototype.rotate = function(n) {
    while (this.length && n < 0) n += this.length;
    this.push.apply(this, this.splice(0, n));
    return this;
}
24
répondu Gumbo 2009-12-31 19:36:45

Cette fonction fonctionne dans les deux sens et fonctionne avec n'importe quel nombre (même avec un nombre supérieur à la longueur du tableau):

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

Exemple:

for(let i = -6 ; i <= 6 ; i++)
  console.log( arrayRotate( ["H","e","l","l","o"], i).join(''), i )

Résultat:

"oHell", -6
"Hello", -5
"elloH", -4
"lloHe", -3
"loHel", -2
"oHell", -1
"Hello",  0
"elloH",  1
"lloHe",  2
"loHel",  3
"oHell",  4
"Hello",  5
"elloH",  6
15
répondu Yukulélé 2016-07-13 12:33:35

Tant de ces réponses semblent trop compliquées et difficiles à lire. Je ne pense pas avoir vu quelqu'un utiliser splice avec 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;
}

Console.sorties du journal (*générées en mai):

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

En ce qui concerne la compacité, je peux offrir quelques fonctions génériques one-liner (sans compter la console.journal | retour). Juste le nourrir, le tableau et la valeur cible dans les arguments.

Je combine ces fonctions en une seule pour un programme de jeu de cartes à quatre joueurs où le le tableau est ['N', 'E','S','W']. Je les ai laissés séparés au cas où quelqu'un voudrait copier/coller pour leurs besoins. Pour mes besoins, j'utilise les fonctions lors de la recherche dont le tour est à côté de jouer / agir pendant différentes phases du jeu (Pinochle). Je n'ai pas pris la peine de tester la vitesse, donc si quelqu'un d'autre le veut, n'hésitez pas à me faire connaître les résultats.

*Remarque, la seule différence entre les fonctions est le "+ 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;
}

Combinaison fonction...

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
5
répondu mickmackusa 2015-05-30 04:22:00

@Christoph, vous avez fait un code propre, mais 60% plus lent que ce que j'ai trouvé. Regardez le résultat sur jsPerf : http://jsperf.com/js-rotate-array/2 [Edit] OK maintenant il y a plus de navigateurs et ce ne sont pas des méthodes de sorcière évidentes les meilleures

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
3
répondu molokoloco 2011-10-22 22:47:58

Voir 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
répondu Vic99999 2013-06-16 16:34:26

La réponse acceptée a un défaut de ne pas être capable de gérer des tableaux plus grands que la taille de la pile d'appels qui dépend de la session mais devrait être autour de 100~300K éléments. Par exemple, dans la session Chrome actuelle que j'ai essayé, c'était 250891. Dans de nombreux cas, vous ne savez même pas à quelle taille le tableau pourrait se développer dynamiquement. Donc, c'est un grave problème.

Pour surmonter cette limitation, je suppose qu'une méthode intéressante est d'utiliser Array.prototype.map() et de cartographier les éléments en réorganisant le indices de manière circulaire. Cette méthode prend un argument entier. Si cet argument est positif, il tournera sur les indices croissants et si négatif sur la direction des indices décroissants. Cela n'a Qu'une complexité temporelle O(n) et retournera un nouveau tableau sans muter celui auquel il est appelé tout en manipulant des millions d'éléments sans aucun problème. Voyons comment cela fonctionne;

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 Fait après j'ai été critiqué sur deux questions comme suit;

  1. Il y a pas besoin d'un conditionnel pour l'entrée positive ou négative car il révèle une violation de sec.vous pouvez le faire avec une carte parce que chaque négatif n a un équivalent positif (tout à fait raison..)
  2. une fonction de tableau devrait soit changer le tableau actuel ou faire un nouveau tableau, votre fonction pourrait faire soit selon qu'un décalage est nécessaire ou non (tout à fait raison..)

, j'ai décidé de modifier le code comme suit;

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

Puis à nouveau; de bien sûr, les foncteurs JS comme Array.prototype.map() sont lents par rapport à leurs équivalents codés en js simple. Afin de gagner plus de 100% d'augmentation des performances suivantes serait probablement mon choix de Array.prototype.rotate() si jamais j'ai besoin pour faire pivoter un tableau dans le code de production comme celui que j'ai utilisé dans ma tentative sur String.prototype.diff()

Array.prototype.rotate = function(n){
  var len = this.length,
      res = new Array(this.length);
  if (n % len === 0) return this.slice();
  else for (var i = 0; i < len; i++) res[i] = this[(i + (len + n % len)) % len];
  return res;
};
2
répondu Redu 2016-07-04 09:01:30

Voici un moyen très simple de déplacer les éléments dans un tableau:

function rotate(array, stepsToShift) {

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

    return array;
}
2
répondu Aryeh Harris 2016-09-06 18:32:25

Cette fonction est un peu plus rapide que la réponse acceptée pour les petits tableaux mais beaucoup plus rapide pour les grands tableaux. Cette fonction permet également un nombre arbitraire de rotations supérieur à la longueur du tableau, ce qui est une limitation de la fonction d'origine.

Enfin, la réponse acceptée tourne dans la direction opposée comme décrit.

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

Et l'équivalent fonctionnel (qui semble aussi avoir des avantages de performance):

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

Vous pouvez consulter le répartition des performances ici.

2
répondu Tanner Stults 2016-10-18 04:42:26

Quand Je ne pouvais pas trouver un extrait prêt à l'emploi pour commencer une liste de jours avec 'today', Je l'ai fait comme ceci (pas tout à fait Générique, probablement beaucoup moins raffiné que les exemples ci-dessus, mais j'ai fait le travail):

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

Grâce à un petit refactor par un meilleur programmeur que moi, c'est une ligne ou deux plus courte que ma tentative initiale, mais tout autre commentaire sur l'efficacité est le bienvenu.

2
répondu Dave Everitt 2018-07-16 12:43:02

Comment incrémenter un compteur, puis obtenir le reste d'une division par la longueur du tableau pour obtenir où vous êtes censé être.

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

Syntaxe du langage mis à part cela devrait fonctionner correctement.

0
répondu tgandrews 2009-12-31 13:40:57

Si votre tableau sera grand et / ou que vous allez beaucoup tourner, vous voudrez peut-être envisager d'utiliser une liste liée au lieu d'un tableau.

0
répondu Thomas Eding 2010-01-01 10:10:12

@molokoloco j'avais besoin d'une fonction que je pouvais configurer pour tourner dans une direction - true pour forward et false pour backward. J'ai créé un extrait qui prend une direction, un compteur et un tableau et génère un objet avec le compteur incrémenté dans la direction appropriée ainsi que les valeurs prior, current et next. Il ne modifie pas le tableau d'origine.

Je l'ai aussi cadencé contre votre extrait et bien qu'il ne soit pas plus rapide, il est plus rapide que ceux que vous comparez le vôtre avec-21% plus lent http://jsperf.com/js-rotate-array/7 .

function directionalRotate(direction, counter, arr) {
  counter = direction ? (counter < arr.length - 1 ? counter + 1 : 0) : (counter > 0 ? counter - 1 : arr.length - 1)
  var currentItem = arr[counter]
  var priorItem = arr[counter - 1] ? arr[counter - 1] : arr[arr.length - 1]
  var nextItem = arr[counter + 1] ? arr[counter + 1] : arr[0]
  return {
    "counter": counter,
    "current": currentItem,
    "prior": priorItem,
    "next": nextItem
  }
}
var direction = true // forward
var counter = 0
var arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'];

directionalRotate(direction, counter, arr)
0
répondu saranicole 2012-10-04 18:46:12

Je viens en retard mais j'ai une brique à ajouter à ces bonnes réponses. On m'a demandé de coder une telle fonction et j'ai d'abord fait:

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

Mais il semblait être moins efficace que de suivre quand n est grand:

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
répondu antoni 2017-09-15 02:12:58

Modifier:: Il s'avère qu'il y a trop d'itérations. Pas de boucles, pas de ramification.

Fonctionne toujours avec n négatif pour la rotation à droite et n positif pour la rotation à gauche pour n'importe quelle taille n, La Mutation de libre -

function rotate(A,n,l=A.length) {
  const offset = (((n % l) + l) %l)
  return A.slice(offset).concat(A.slice(0,offset))
}
Voici la version Code golf pour les rires
const r = (A,n,l=A.length,i=((n%l)+l)%l)=>A.slice(i).concat(A.slice(0,i))

EDIT1:: * Dépourvu de branches, mutationless mise en œuvre.

Il s'avère que j'avais une branche où je n'en avais pas besoin. Voici une solution de travail. num négatif = rotation à droite par |num| positif num = gauche tourner par num
function r(A,n,l=A.length) {
  return A.map((x,i,a) => A[(((n+i)%l) + l) % l])
}

L'équation ((n%l) + l) % l mappe exactement les nombres positifs et négatifs de toutes les valeurs arbitrairement grandes de n

ORIGINAL

Tournez à gauche et à droite. Tourner à gauche avec positif n, tourner à droite avec négatif n.

Fonctionne pour des entrées obscènes de n.

Pas de mode de mutation. Trop de mutation dans ces réponses.

Aussi, moins d'opérations que la plupart des réponses. Pas de pop, pas de poussée, aucune épissure, pas de changement.

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

Explication:

Mapper chaque index de A à la valeur au décalage de l'index. Dans ce cas

offset = num

Si le offset < 0 alors offset + index + positive length of A pointera vers le décalage inverse.

Si offset > 0 and offset < length of A alors il suffit de mapper l'index actuel à L'indice de décalage de A.

Sinon, modulo le décalage et la longueur pour mapper le décalage dans les limites du tableau.

Par exemple offset = 4 et offset = -4.

Quand offset = -4, et A = [1,2,3,4,5], pour chaque indice, offset + index rendra la grandeur (ou Math.abs(offset)) plus petite.

Expliquons d'abord le calcul de l'indice de n négatif. A[(((n % A.length) + A.length) % A.length)+0] et a été intimidé. Ne pas l'être.{[42] } Il m'a fallu 3 minutes dans un Repl pour le résoudre.

  1. Nous savons n est négatif parce que le cas est n < 0. Si le nombre est supérieur à la plage du tableau, n % A.length le mappera dans la plage.
  2. n + A.length ajoutez ce nombre à A.length pour correct montant.
  3. Nous savons n est négatif parce que le cas est n < 0. n + A.length ajoutez ce nombre à A.length pour compenser n Le montant correct.
  4. Suivant mapper à la plage de la longueur d'un en utilisant modulo. Le second module est nécessaire pour mapper le résultat du calcul dans une plage indexable

    entrez la description de l'image ici

  5. Premier indice: -4 + 0 = -4. A. longueur = 5. A. longueur - 4 = 1. Un2 est 2. Carte index 0 à 2. [2,... ]

  6. index suivant, -4 + 1 = -3. 5 + -3 = 2. Un2 est 3. Carte index 1 à 3. [2,3... ]
  7. etc.

, Le même processus s'applique à offset = 4. Quand offset = -4, et A = [1,2,3,4,5], pour chaque indice, offset + index rendra la grandeur plus grande.

  1. 4 + 0 = 0. Mappez a [0] à la valeur A [4]. [5...]
  2. 4 + 1 = 5, 5 est hors limites lors de l'indexation, donc Mapper A2 à la valeur au reste de 5 / 5, qui est 0. Un2 = valeur à A [0]. [5,1...]
  3. répéter.
0
répondu nathan rogers 2018-04-28 06:12:41

En utilisant la propagation D'ES6 pour un exemple immuable ...

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

Et

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

Ce n'est probablement pas le plus efficace mais c'est concis.

0
répondu Dudley Craig 2018-10-04 11:42:52