JavaScript Array Rotate ()

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

Je suis venu avec cette solution, où un n positif tourne le tableau vers la droite, et un négatif n vers la gauche ( -length < n < length ):

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

Ce qui peut ensuite ê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 l'a souligné Christoph dans les commentaires ci-dessous, une version correcte est (le retour supplémentaire permet le chaînage):

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

Existe-t-il une solution plus compacte et / ou plus rapide, éventuellement dans le cadre d'un cadre JavaScript? (Aucune des versions proposées ci-dessous n'est plus compacte ou plus rapide)

Existe-t-il un cadre JavaScript là-bas avec un tableau de rotation intégré? (Toujours pas répondu par personne)

Type-safe, version générique qui modifie 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 selon lequel le code ne supporte pas la surcharge de push() et splice() . Je ne pense pas que cela soit vraiment utile (voir les commentaires), mais une solution rapide (un peu de 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)); 

L'utilisation de unshift() au lieu de push() est presque deux fois plus rapide dans Opera 10, alors que les différences de 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; }; })(); 

EDIT: voir aussi ma nouvelle réponse avec l'argument count : https://stackoverflow.com/a/33451102/806169

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

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

usage:

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

Je ferais probablement quelque chose comme ça:

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

Modifier Voici une version de mutator:

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

Cette fonction fonctionne de la même manière 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 

@Christoph, vous avez fait un code propre, mais 60% plus lent que celui que j'ai trouvé. Regardez le résultat sur jsPerf: http://jsperf.com/js-rotate-array/2 [Modifier] OK maintenant, il y a plus de navigateurs et les méthodes de sorcière évidentes sont 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 

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

Sorties console.log (* 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 proposer quelques fonctions génériques unilatérales (sans compter la partie de la console.log | retour). Il suffit de l'alimenter par le tableau et la valeur cible dans les arguments.

Je combine ces fonctions en un pour un programme de jeu de cartes à quatre joueurs où le tableau est ['N', 'E', 'S', 'W']. Je les ai séparés au cas où quelqu'un veut copier / coller pour leurs besoins. Pour mes besoins, j'utilise les fonctions lors de la recherche du tour qui est à côté de jouer / agir pendant différentes phases du jeu (Pinochle). Je n'ai pas dérangé de tester la vitesse, alors si quelqu'un d'autre 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; } 

Fonction combinée …

 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 

Lorsque je n'ai pas trouvé un extrait prêt pour commencer une liste de jours avec 'aujourd'hui', je l'ai fait comme ça (pas assez générique, probablement beaucoup moins raffiné que les exemples ci-dessus, mais a fait le travail):

 //returns 7 day names with today first function startday() { var days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; var today = new Date(); var 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)) } } 

Merci à un peu de réfactor par un meilleur programmeur que moi, c'est une ligne ou deux plus courte que ma tentative initiale, mais d'autres commentaires sur l'efficacité sont les bienvenus.

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

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

Pour surmonter cette limitation, je suppose qu'une méthode intéressante utilise Array.prototype.map() et la cartographie des éléments en réarrangant les indices de façon circulaire. Cette méthode prend un argument entier. Si cet argument est positif, il tourne sur l'augmentation des indices et si négatif sur la direction des indices décroissants. Cela n'a que la complexité du temps O (n) et renverra un nouveau tableau sans mutation de celui auquel il est appelé lors de la gestion de millions d'articles sans aucun problème. Laisse voir comment ça marche;

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

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

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

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

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

La syntaxe de la langue, cela devrait fonctionner correctement.

Si votre tableau va être grand et / ou vous allez tourner beaucoup, vous pouvez envisager d'utiliser une liste liée au lieu d'un tableau.

@molokoloco J'ai besoin d'une fonction que je pourrais configurer pour faire pivoter dans une direction – true pour forward et false pour l'arrière. J'ai créé un extrait qui prend une direction, un compteur et un tableau et produit un objet avec le compteur incrémenté dans la direction appropriée ainsi que les valeurs antérieures, courantes et suivantes. Il ne modifie PAS le tableau d'origine.

Je l'ai également synchronisé contre votre extrait et, bien qu'il ne soit pas plus rapide, il est plus rapide que ceux que vous comparez les vôtres 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) 

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

Enfin, la réponse acceptée tourne en sens inverse 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 avoir également des avantages pour la 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 la répartition des performances ici.