Je me demandais quel était le moyen le plus efficace de faire pivoter un JavaScript array.
Je suis arrivé avec cette solution, où une n
positive fait pivoter le tableau vers la droite, et une n
négative vers la gauche (-length < n < length
):
Array.prototype.rotateRight = function( n ) {
this.unshift( this.splice( n, this.length ) )
}
Ce 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 l'a souligné Christoph dans les commentaires ci-dessous, une version correcte est (le retour supplémentaire permet l'enchaînement):
Array.prototype.rotateRight = 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 contexte d’un framework JavaScript? (aucune des versions proposées ci-dessous n'est plus compacte ni plus rapide)
Existe-t-il un framework JavaScript avec une rotation de tableau intégrée? (Toujours pas répondu par personne)
Type, 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 suivant: le code ne prend pas en charge la surcharge de Push()
et splice()
. Je ne pense pas que cela soit vraiment utile (voir commentaires), mais une solution rapide (un peu un bidouillage, 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 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;
};
})();
Vous pouvez utiliser les fonctions Push()
, pop()
, shift()
et unshift()
:
function arrayRotateOne(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'];
Si vous avez besoin de l'argument count
, consultez mon autre réponse: https://stackoverflow.com/a/33451102
Je ferais probablement quelque chose comme ça:
Array.prototype.rotate = function(n) {
return this.slice(n, this.length).concat(this.slice(0, n));
}
Edit Voici une version mutatrice:
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 dans les deux sens et avec n'importe quel nombre (même si le nombre est 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
Tant de réponses semblent trop compliquées et difficiles à lire ... Je ne crois pas avoir vu quelqu'un utiliser l'épissure avec du 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 à une ligne (sans compter la partie console.log | return). Il suffit de 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 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 lorsque je cherche le tour suivant de jouer/jouer durant les différentes phases du jeu (Pinochle). Je n'ai pas pris la peine de tester la vitesse, alors si quelqu'un d'autre le souhaite, n'hésitez pas à me le faire savoir.
* 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 de combinaison ...
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);
tableau ajusté =
W,N,E,S
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);
}
}
@Christoph, vous avez fait un code propre, mais 60% plus lent que celui-ci que j'ai trouvé. Regardez le résultat sur jsPerf: http://jsperf.com/js-rotate-array/2 [Edit] OK, il y a maintenant plus de navigateurs que de méthodes non évidentes.
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
Quand je ne pouvais pas trouver d'extrait prêt à l'emploi pour commencer une liste de jours avec 'aujourd'hui', 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))
}
}
Merci à un petit refactor par un meilleur programmeur que moi, c'est une ligne ou deux plus courte que ma tentative initiale, mais tout commentaire supplémentaire sur l'efficacité est le bienvenu.
La réponse acceptée a le défaut de ne pas pouvoir gérer des tableaux plus grands que la taille de la pile d’appel, qui dépend de la session mais doit se situer autour de 100 ~ 300K éléments. Par exemple, dans la session Chrome actuelle que j’ai essayée, il s’agissait de 250891. Dans de nombreux cas, vous pouvez même ne pas savoir à quelle taille le tableau peut se développer de manière dynamique. C'est donc un problème grave.
Pour surmonter cette limitation, je suppose qu'une méthode intéressante consiste à utiliser Array.prototype.map()
et à cartographier les éléments en réorganisant les indices de manière circulaire. Cette méthode prend un argument entier. Si cet argument est positif, il alternera avec les indices croissants et, s'il est négatif, avec une direction décroissante. Cela n'a que O(n) temps complexe et renverra 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 avoir été critiqué pour deux raisons,
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 encore; Bien entendu, les foncteurs JS comme Array.prototype.map()
sont lents par rapport à leurs équivalents codés en JS simple. Pour gagner plus de 100% de performances, voici mon choix de Array.prototype.rotate()
si je dois faire pivoter un tableau dans le code de production comme celui que j'ai utilisé lors de 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;
};
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 constitue une limitation de la fonction d'origine.
Enfin, la réponse acceptée tourne dans le sens opposé, 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 également présenter des avantages en termes de performances):
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 ventilation performance ici.
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;
}
Utiliser ES6's spread 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.
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));
var arr = ['a','b','c','d']
arr.slice(1,arr.length).concat(arr.slice(0,1)
var arr = ['a','b','c','d']
arr = arr.concat(arr.splice(0,1))
@molokoloco J'avais besoin d'une fonction que je pouvais configurer pour effectuer une rotation 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 antérieures, actuelles et suivantes. Il ne modifie pas le tableau d'origine.
Je l'ai également synchronisé avec votre extrait et bien qu'il ne soit pas plus rapide, il est plus rapide que ceux avec lesquels vous comparez le vôtre - 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)
EDIT :: Hey, il s'avère qu'il y a trop d'itérations en cours. Pas de boucles, pas de branchement.
Fonctionne toujours avec n négatif pour la rotation à droite et n positif pour la rotation à gauche pour toute taille n, Mutation free
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 de code de golf pour rire
const r = (A,n,l=A.length,i=((n%l)+l)%l)=>A.slice(i).concat(A.slice(0,i))
EDIT1 :: * Implémentation sans branche, sans mutation.
Alors hé, il se trouve que j'avais une branche où je n'en avais pas besoin. Voici une solution de travail. négatif num = rotation à droite de | num | positif num = rotation à gauche de 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 toute valeur arbitrairement grande de n
ORIGINAL
Tourner à gauche et à droite. Faites pivoter à gauche avec n
positif, tournez à droite avec n
négatif.
Fonctionne pour des entrées obscènes de n
.
Aucun mode de mutation. Trop de mutation dans ces réponses.
En outre, moins d'opérations que la plupart des réponses. Pas de pop, pas de push, pas d'épissure, pas de décalage.
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 d'index. Dans ce cas
offset = num
si le offset < 0
puis offset + index + positive length of A
pointera vers le décalage inverse.
si offset > 0 and offset < length of A
, mappez simplement l'index actuel sur l'index de décalage de A.
Sinon, modulo le décalage et la longueur pour mapper le décalage dans les limites du tableau.
Prenons par exemple offset = 4
et offset = -4
.
Lorsque offset = -4
et A = [1,2,3,4,5]
, pour chaque index, offset + index
réduira la magnitude (ou Math.abs(offset)
).
Expliquons le calcul de l'indice de n négatif en premier. A[(((n % A.length) + A.length) % A.length)+0]
et été intimidé. Ne sois pas. Il m'a fallu 3 minutes en réplique pour résoudre le problème.
n
est négatif car le cas est n < 0
. Si le nombre est supérieur à la plage du tableau, n % A.length
le mappera dans la plage. n + A.length
ajouter ce nombre à A.length
pour compenser n le montant correct n
est négatif car le cas est n < 0
. n + A.length
ajouter ce nombre à A.length
pour compenser n le montant correct. Next Mappez-le sur la plage de longueur de A en utilisant modulo. Le deuxième modulable est nécessaire pour mapper le résultat du calcul dans une plage indexable
Premier indice: -4 + 0 = -4. A.length = 5. A.length - 4 = 1. A 2 est 2. Index de la carte 0 à 2. [2,... ]
[2,3... ]
Le même processus s'applique à offset = 4
. Lorsque offset = -4
et A = [1,2,3,4,5]
, pour chaque index, offset + index
augmentera la magnitude.
Je partage ma solution que j'utilise pour la rotation sur carrousel. Cela peut casser lorsque la taille du tableau est inférieure à displayCount
, mais vous pouvez ajouter une condition supplémentaire pour arrêter la rotation lorsqu'elle est petite ou concaténer le tableau principal * fois le nombre de fois displayCount.
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]
Je ne suis pas sûr que ce soit le moyen le plus efficace, mais j'aime bien la façon dont il se lit, il est assez rapide pour la plupart des tâches volumineuses, car je l'ai testé en production ...
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()
J'arrive en retard mais j'ai une brique à ajouter à ces bonnes réponses… .. On m'a demandé de coder une telle fonction et je l'ai d'abord fait:
Array.prototype.rotate = function(n)
{
for (var i = 0; i < n; i++)
{
this.Push(this.shift());
}
return this;
}
Mais cela semblait moins efficace que de suivre lorsque n
est gros:
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]
});
}
Pourquoi ne pas incrémenter un compteur, puis obtenir le reste d’une division par la longueur du tableau pour arriver où vous êtes supposé être.
var i = 0;
while (true);
{
var position = i % months.length;
alert(months[position]);
++i;
}
La syntaxe de langage mise à part, cela devrait fonctionner correctement.
Si votre tableau doit être grand et/ou que vous allez effectuer beaucoup de rotations, vous pouvez envisager d'utiliser une liste chaînée au lieu d'un tableau.