Drehen Sie die Elemente in einem Array in JavaScript


86

Ich habe mich gefragt, wie ein JavaScript-Array am effizientesten gedreht werden kann.

Ich habe mir diese Lösung ausgedacht, bei der ein Positiv ndas Array nach rechts und ein Negativ nnach links dreht ( -length < n < length):

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

Welches kann dann so verwendet werden:

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

Meine ursprüngliche Version oben weist einen Fehler auf, wie Christoph in den Kommentaren unten ausgeführt hat. Eine korrekte Version ist (die zusätzliche Rückgabe ermöglicht eine Verkettung):

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

Gibt es eine kompaktere und / oder schnellere Lösung, möglicherweise im Kontext eines JavaScript-Frameworks? (Keine der vorgeschlagenen Versionen unten ist entweder kompakter oder schneller)

Gibt es ein JavaScript-Framework mit einem integrierten Array-Rotations-Framework? (Immer noch von niemandem beantwortet)


1
Ich verstehe nicht, was Ihr Beispiel tun soll. Warum verwenden Sie nicht einfach months[new Date().getMonth()], um den Namen des aktuellen Monats abzurufen?
Gumbo

1
@Jean: Der Code ist kaputt: So wie Sie es tun, werden die gespleißten Elemente als Array und nicht einzeln verschoben. Sie müssen verwenden apply(), damit Ihre Implementierung funktioniert
Christoph

Heute würde die Rotation Monate ändern, um diese Liste anzuzeigen (mit Decan erster Stelle):["Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"]
Jean Vincent

@Christoph, Sie haben Recht, dies würde nicht als generische Drehfunktion funktionieren. Es funktioniert nur, wenn es direkt danach zum Konvertieren in einen String verwendet wird.
Jean Vincent

@ Jean: Sie können Ihre Version reparieren über Array.prototype.unshift.apply(this, this.splice(...))- meine Version macht das gleiche, verwendet aber push()anstelle vonunshift()
Christoph

Antworten:


60

Typensichere, generische Version, die das Array mutiert:

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

In den Kommentaren sprach Jean das Problem an, dass der Code das Überladen von push()und nicht unterstützt splice(). Ich denke nicht, dass dies wirklich nützlich ist (siehe Kommentare), aber eine schnelle Lösung (allerdings ein bisschen wie ein Hack) wäre, die Leitung zu ersetzen

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

mit diesem:

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

Die Verwendung von unshift()anstelle von push()ist in Opera 10 fast doppelt so schnell, während die Unterschiede in FF vernachlässigbar waren. der 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;
    };
})();

2
Nettes Caching von Array.prototypeMethoden! +1
James

Sehr schöne kugelsichere Implementierung, aber im Allgemeinen würde ich mich bei schlechter Verwendung lieber auf Ausnahmen oder einfach nur schlechte Antworten verlassen. Dies, um den Code sauber und schnell zu halten. Es liegt in der Verantwortung des Benutzers, korrekte Parameter zu übergeben oder die Konsequenzen zu tragen. Ich mag die Strafe für gute Benutzer nicht. Davon abgesehen ist dies perfekt, da es das Array wie gewünscht modifiziert und nicht unter dem Fehler in meiner Implementierung leidet, durch den ein Array anstelle der einzelnen Elemente verschoben wurde, wie Sie bemerkt haben. Die Rückgabe ist auch besser, um eine Verkettung zu ermöglichen. So danke.
Jean Vincent

Was kostet die Schließung hier, nur um Push und Splice zwischenzuspeichern?
Jean Vincent

@ Jean: Nun, Verschlüsse erfassen die gesamte Scope-Kette. Solange sich die äußere Funktion auf oberster Ebene befindet, sollte die Auswirkung vernachlässigbar sein und ohnehin O (1) betragen, während das Nachschlagen der Array-Methoden in der Anzahl der Aufrufe O (n) ist. Das Optimieren von Implementierungen könnte die Suche inline machen, so dass es keinen Gewinn gibt, aber mit den ziemlich dummen Interpreten, mit denen wir uns lange beschäftigen mussten, könnte das Zwischenspeichern von Variablen in einem niedrigeren Bereich erhebliche Auswirkungen haben
Christoph,

1
Dadurch werden keine großen Arrays gedreht. Ich habe heute nachgesehen und es konnte nur eine Reihe von 250891 Artikeln in der Länge verarbeiten. Anscheinend ist die Anzahl der Argumente, die Sie mit der applyMethode übergeben können, bei einer bestimmten Aufrufstapelgröße begrenzt. In ES6-Begriffen würde der Spread-Operator auch unter dem gleichen Problem mit der Stapelgröße leiden. Unten gebe ich eine Kartenmethode, um das Gleiche zu tun. Es ist langsamer, funktioniert aber für Millionen von Gegenständen. Sie können die Karte auch mit einer einfachen for- oder while-Schleife implementieren, die viel schneller wird.
Reduzieren Sie den

144

Sie können verwendet werden push(), pop(), shift()und unshift()Methoden:

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

Verwendung:

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

Wenn Sie countArgumente benötigen, lesen Sie meine andere Antwort: https://stackoverflow.com/a/33451102 🖤🧡💚💙💜


Es zählt nicht, obwohl das je nach Verwendung keine große Sache ist.
JustGage

@ JustGage, ich veröffentliche eine andere Antwort mit Zählunterstützung stackoverflow.com/questions/1985260/…
Yukulélé

2
Nur ein Hinweis: Beachten Sie, dass diese Methode aufdringlich ist und das als Parameter gesendete Array manipuliert. Es ist nicht schwer, das Duplizieren des Arrays vor dem Senden
fguillen

Hier ist eine Version in Typoskript mit der linken rechten Variablen stackblitz.com/edit/typescript-vtcmhp
Dživo Jelić

38

Ich würde wahrscheinlich so etwas machen:

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

Bearbeiten     Hier ist eine Mutator-Version:

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

Denken Sie daran, dass diese Funktion das ursprüngliche Array unverändert lässt
Christoph

Es muss das ursprüngliche Array (dies) ändern, genau wie Push, Pop, Shift, Unhift, Concat und Splice. Ansonsten gilt dies.
Jean Vincent

@ Gumbo, warum die while-Schleife? Wir müssen nicht n positiv machen, Spleiß funktioniert genauso gut mit negativen Werten. Am Ende ist dies die richtige, aber ziemlich christliche Version, die es zuerst richtig gemacht hat, ohne die Überlastungsbeschränkung.
Jean Vincent

@Gumbo, Korrektur meines vorherigen Kommentars, negative Zahlen funktionieren nur in der Spleißversion (n. This.length). Die Verwendung von Spleiß (0, n) wie in Ihrer Version erfordert ein positives int.
Jean Vincent

1
Ich habe n = n % this.lengthvor der return-Anweisung hinzugefügt , um negative und / oder außerhalb der Grenzen liegende Zahlen zu behandeln.
iedmrc

25

Diese Funktion funktioniert in beide Richtungen und funktioniert mit einer beliebigen Zahl (auch mit einer Zahl, die größer als die Array-Länge ist):

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

Verwendung:

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

Ergebnis:

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

Mit dieser Funktion bin ich auf ein Problem gestoßen, weil es das ursprüngliche Array verändert. Ich habe hier einige Problemumgehungen
ognockocaten

1
@ognockocaten Es ist kein Problem, es ist, wie diese Funktion funktioniert. Wenn Sie das ursprüngliche Array unverändert lassen möchten, klonen Sie es vorher:var arr2 = arrayRotate(arr.slice(0), 5)
Yukulélé

Dies ist in der Tat eine großartige Antwort und hat mir geholfen. Es ist jedoch am besten, eine Alternative bereitzustellen, die das ursprüngliche Array nicht mutiert. Sicher, es ist einfach, nur eine Kopie des Originals zu erstellen. Dies ist keine bewährte Methode, da der Speicher unnötig belegt wird.
Hybrid Web Dev

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

7

So viele dieser Antworten scheinen zu kompliziert und schwer zu lesen. Ich glaube nicht, dass ich jemanden gesehen habe, der Spleiß mit Concat verwendet hat ...

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.log-Ausgaben (* generiert im Mai):

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

In Bezug auf die Kompaktheit kann ich einige allgemeine Einzeilerfunktionen anbieten (ohne den Teil console.log | return). Geben Sie einfach das Array und den Zielwert in die Argumente ein.

Ich kombiniere diese Funktionen zu einer für ein Kartenspielprogramm für vier Spieler, bei dem das Array ['N', 'E', 'S', 'W'] ist. Ich habe sie getrennt gelassen, falls jemand für seine Bedürfnisse kopieren / einfügen möchte. Für meine Zwecke benutze ich die Funktionen, wenn ich suche, wer an der Reihe ist, in verschiedenen Phasen des Spiels zu spielen / zu handeln (Pinochle). Ich habe mich nicht darum gekümmert, auf Geschwindigkeit zu testen. Wenn jemand anderes dies möchte, kann er mich gerne über die Ergebnisse informieren.

* Beachten Sie, der einzige Unterschied zwischen den Funktionen ist "+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;
}

Kombinationsfunktion ...

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

6

Verwendung des ES6-Spread als unveränderliches Beispiel ...

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

und

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

Es ist wahrscheinlich nicht das effizienteste, aber es ist prägnant.


5

Einfache Lösung mit Slice und Destructuring:

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
Das ist wirklich schön! Sie können die Notwendigkeit einer Überprüfung vermeiden, count === 0indem Sie einen Standardwert festlegen. Das würde es Ihnen ermöglichen, dies zu einem const rotate = (arr, n = 1) => [...arr.slice(n, arr.length), ...arr.slice(0, n)];
Nick F

@ NickF schön, danke! Habe nicht so gedacht.
Kashsandr

4

Hier ist eine sehr einfache Möglichkeit, Elemente in einem Array zu verschieben:

function rotate(array, stepsToShift) {

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

    return array;
}

3

@Christoph, du hast einen sauberen Code gemacht, aber 60% langsamer als dieser, den ich gefunden habe. Schauen Sie sich das Ergebnis auf jsPerf an: http://jsperf.com/js-rotate-array/2 [Bearbeiten] OK, jetzt gibt es mehr Browser und die nicht offensichtlichen Hexenmethoden am besten

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 Intuitiv würde Ihre Lösung langsamer laufen, da das Inkrement höher wäre, weil Sie eine Schleife verwenden. Ich habe Ihren Testfall mit einem Inkrement von 5 aktualisiert und er scheint dann langsamer zu laufen: jsperf.com/js-rotate-array/3
Jean Vincent

Ich kann mir nicht vorstellen, was die Funktion rotateArray () macht ^^ nur, dass es funktioniert :) (es ist ein seltsamer Code!) Chrome verhält sich fast umgekehrt wie Firefox ...
Molokoloco

Diese Methode ist sehr interessant und funktioniert durch Vertauschen von Elementreferenzen im Array. Die Arraygröße ändert sich nie, weshalb sie mit dem Nachteil der Verwendung von Schleifen sehr schnell ist. Das Interessante daran ist, dass Firefox bei Loops am schnellsten ist, während Chrome bei Arrays-Manipulationen am schnellsten ist.
Jean Vincent

1
Nach der Analyse des Codes habe ich eine Schwachstelle festgestellt, die zeigt, dass diese Lösung nur für kleinere Arrays und bestimmte Rotationszahlen schneller ist: jsperf.com/js-rotate-array/5 Unter allen Browsern bleibt Google Chrome mit dem Splice / Unshift der schnellste Lösung. Dies bedeutet, dass dies wirklich eine Frage der Optimierung der Array-Methode ist, die Chrome besser als die anderen Browser macht.
Jean Vincent

3

Siehe 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

Als ich kein fertiges Snippet finden konnte, um eine Liste von Tagen mit "Heute" zu beginnen, habe ich es so gemacht (nicht ganz allgemein, wahrscheinlich weit weniger verfeinert als die obigen Beispiele, aber den Job gemacht):

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

Dank eines kleinen Refaktors von einem besseren Programmierer als mir ist es ein oder zwei Zeilen kürzer als mein erster Versuch, aber weitere Kommentare zur Effizienz sind willkommen.


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 einer Codezeile:

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

2

Die akzeptierte Antwort weist den Fehler auf, dass Arrays, die größer als die Größe des Aufrufstapels sind, nicht verarbeitet werden können. Dies hängt von der Sitzung ab, sollte jedoch etwa 100 bis 300.000 Elemente umfassen. In der aktuellen Chrome-Sitzung, in der ich es ausprobiert habe, war es beispielsweise 250891. In vielen Fällen wissen Sie möglicherweise nicht einmal, in welche Größe das Array dynamisch hineinwachsen könnte. Das ist also ein ernstes Problem.

Um diese Einschränkung zu überwinden, besteht eine interessante Methode darin, Array.prototype.map()die Elemente zu verwenden und abzubilden, indem die Indizes kreisförmig neu angeordnet werden. Diese Methode verwendet ein ganzzahliges Argument. Wenn dieses Argument positiv ist, dreht es sich bei steigenden Indizes und wenn es bei abnehmender Indexrichtung negativ ist. Dies hat nur eine O (n) -Zeitkomplexität und gibt ein neues Array zurück, ohne das aufgerufene zu mutieren, während Millionen von Elementen problemlos verarbeitet werden. Mal sehen, wie es funktioniert;

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

Eigentlich, nachdem ich in zwei Punkten wie folgt kritisiert worden bin;

  1. Es ist keine Bedingung für positive oder negative Eingaben erforderlich, da dies eine Verletzung von DRY aufzeigt. Sie können dies mit einer Karte tun, da jedes negative n ein positives Äquivalent hat (völlig richtig ..).
  2. Eine Array-Funktion sollte entweder das aktuelle Array ändern oder ein neues Array erstellen. Ihre Funktion kann entweder abhängig davon, ob eine Verschiebung erforderlich ist oder nicht (völlig richtig ..).

Ich habe beschlossen, den Code wie folgt zu ändern:

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

Dann wieder; Natürlich sind die JS-Funktoren Array.prototype.map()im Vergleich zu ihren in einfachem JS codierten Äquivalenten langsam. Um mehr als 100% Leistungssteigerung zu erzielen, würde ich wahrscheinlich Folgendes wählen, Array.prototype.rotate()wenn ich jemals ein Array im Produktionscode drehen muss, wie ich es bei meinem Versuch verwendet habeString.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 () und splice (), so langsam sie auch sein mögen, sind tatsächlich schneller als die Verwendung von map () oder einer Methode, die einen Funktionsparameter verwendet. Shift () und Splice (), die deklarativer sind, lassen sich auch langfristig leichter optimieren.
Jean Vincent

@ Jean Vincent Ja, Sie haben Recht. Die Karte könnte sich tatsächlich als langsamer herausstellen, aber ich glaube, die ideale rotierende Logik ist diese. Ich habe nur versucht, den logischen Ansatz zu zeigen. Die Karte kann leicht mit einer for-Schleife vereinfacht werden und würde zu einer signifikanten Verbesserung der Geschwindigkeit führen ... Die akzeptierte Antwort hat jedoch einen Ansatz, der unter Berücksichtigung einiger anderer Sprachen entwickelt wurde. Es ist nicht ideal für JS .... wird nicht einmal ausgeführt, wenn die Array-Größe größer als die Größe des Aufrufstapels ist. (In meinem Fall und in dieser Sitzung stellte sich heraus, dass es nur 250891 Artikel gab.) Also gibt es das ..!
Reduzieren Sie den

Sie haben Recht mit der Größe des Aufrufstapels, die gelten kann. Dies ist ein gültigeres Argument als die Geschwindigkeit. Bearbeiten Sie Ihre Antwort, um diesen Punkt auch mit einem verbesserten Einzug hervorzuheben.
Jean Vincent

2

Diese Funktion ist etwas schneller als die akzeptierte Antwort für kleine Arrays, aber viel schneller für große Arrays. Diese Funktion ermöglicht auch eine beliebige Anzahl von Umdrehungen, die größer als die Länge des Arrays sind, was eine Einschränkung der ursprünglichen Funktion darstellt.

Zuletzt dreht sich die akzeptierte Antwort wie beschrieben in die entgegengesetzte Richtung.

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

Und das funktionale Äquivalent (das auch einige Leistungsvorteile zu haben scheint):

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

Die Leistungsaufschlüsselung können Sie hier einsehen.


Leider funktioniert es nicht, es verkleinert das Array auf die Größe Null, was erklärt, warum es nach dem ersten Durchlauf so viel schneller ist als jede andere korrekte Implementierung, insbesondere bei größeren Arrays. Wenn Sie in Ihrem Benchmark-Test die Reihenfolge der Tests vertauschen und am Ende eine Länge anzeigen, wird das Problem angezeigt.
Jean Vincent

2

EDIT :: Hey, es stellt sich heraus, dass zu viel Iteration passiert. Keine Schleifen, keine Verzweigung.

Funktioniert immer noch mit negativem n für Rechtsdrehung und positivem n für Linksdrehung für jede Größe n, mutationsfrei

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

Hier ist die Code-Golf-Version für Kichern

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

EDIT1 :: * Branchless, mutationsless Implementierung.

Es stellte sich heraus, dass ich einen Zweig hatte, in dem ich ihn nicht brauchte. Hier ist eine funktionierende Lösung. negative num = rechts um | num | drehen positive num = links um num drehen

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

Die Gleichung ((n%l) + l) % lbildet genau positive und negative Zahlen beliebig großer Werte von n ab

ORIGINAL

Nach links und rechts drehen. Mit positiv nach links ndrehen, mit negativ nach rechts drehenn .

Funktioniert für obszön große Eingaben von n .

Kein Mutationsmodus. Zu viele Mutationen in diesen Antworten.

Außerdem weniger Operationen als die meisten Antworten. Kein Pop, kein Push, kein Spleiß, keine Verschiebung.

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

oder

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

Erläuterung:

Ordnen Sie jeden Index von A dem Wert am Indexversatz zu. In diesem Fall

offset = num

wenn das offset < 0dannoffset + index + positive length of A auf den inversen Versatz zeigt.

wenn offset > 0 and offset < length of A dann, ordnen Sie einfach den aktuellen Index dem Offset-Index von A zu.

Andernfalls modulo den Versatz und die Länge, um den Versatz in den Grenzen des Arrays abzubilden.

Nehmen Sie zum Beispiel offset = 4und offset = -4.

Wann offset = -4und A = [1,2,3,4,5]für jeden Index offset + indexwird die Größe (oder Math.abs(offset)) kleiner.

Lassen Sie uns zuerst die Berechnung für den Index des negativen n erklären. A[(((n % A.length) + A.length) % A.length)+0]und eingeschüchtert worden. Sei nicht. Ich habe 3 Minuten in einem Repl gebraucht, um das herauszufinden.

  1. Wir wissen, dass nes negativ ist, weil der Fall ist n < 0. Wenn die Zahl größer als der Bereich des Arrays ist, n % A.lengthwird sie dem Bereich zugeordnet.
  2. n + A.lengthAddiere diese Zahl zu A.length, um n den richtigen Betrag auszugleichen.
  3. Wir wissen, dass nes negativ ist, weil der Fall ist n < 0. n + A.lengthAddiere diese Zahl zu A.length, um n den richtigen Betrag auszugleichen.
  4. Weiter Ordnen Sie es mit Modulo dem Längenbereich von A zu. Das zweite Modul ist erforderlich, um das Ergebnis der Berechnung in einen indizierbaren Bereich abzubilden

    Geben Sie hier die Bildbeschreibung ein

  5. Erster Index: -4 + 0 = -4. A. Länge = 5. A. Länge - 4 = 1. A 2 ist 2. Kartenindex 0 bis 2.[2,... ]

  6. Nächster Index: -4 + 1 = -3. 5 + -3 = 2. A 2 ist 3. Kartenindex 1 bis 3.[2,3... ]
  7. Etc.

Der gleiche Prozess gilt für offset = 4. Wann offset = -4und A = [1,2,3,4,5]für jeden Index offset + indexwird die Größe größer.

  1. 4 + 0 = 0. Ordnen Sie A [0] dem Wert bei A [4] zu.[5...]
  2. 4 + 1 = 5, 5 ist beim Indizieren außerhalb der Grenzen, also ordne A 2 dem Wert am Rest von zu 5 / 5, der 0 ist. A 2 = Wert bei A [0].[5,1...]
  3. wiederholen.

2
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]

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

Nicht mutierende Lösung

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

mit Mutation

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

1

Ich teile meine Lösung, die ich zum Drehen auf einem Karussell verwende. Es kann zu Unterbrechungen kommen, wenn die Arraygröße kleiner als displayCountist. Sie können jedoch eine zusätzliche Bedingung hinzufügen, um die Rotation zu stoppen, wenn sie klein ist, oder das Hauptarray zu verketten.

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

Wie wäre es, wenn Sie einen Zähler erhöhen und dann den Rest einer Division durch die Array-Länge abrufen, um zu erreichen, wo Sie sein sollen.

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

Abgesehen von der Sprachsyntax sollte dies gut funktionieren.


2
Dadurch wird das Array in keiner Weise gedreht.
Jean Vincent

1
True (wie in der Antwort angegeben), erspart jedoch das Drehen des Arrays.
Tgandrews

1
Der springende Punkt dabei ist jedoch, ein Array gemäß dem Titel zu drehen und kein Array ab einem bestimmten Versatz anzuzeigen. Wenn Sie dieselbe Schleife verwenden würden, um das neue Array neu zu erstellen, wäre es furchtbar langsamer als andere Versionen mit nativen Array-Methoden. Außerdem hast du vergessen, aus der Endlosschleife auszubrechen.
Jean Vincent

0

Wenn Ihr Array groß sein wird und / oder Sie sich viel drehen werden, sollten Sie eine verknüpfte Liste anstelle eines Arrays verwenden.


Einverstanden, aber die Frage bezieht sich auf Arrays und insbesondere auf die Erweiterung von Array-Verben.
Jean Vincent

0

@molokoloco Ich brauchte eine Funktion, die ich so konfigurieren konnte, dass sie sich in eine Richtung dreht - wahr für vorwärts und falsch für rückwärts. Ich habe ein Snippet erstellt, das eine Richtung, einen Zähler und ein Array annimmt und ein Objekt ausgibt, wobei der Zähler in die entsprechende Richtung erhöht wird sowie vorherige, aktuelle und nächste Werte. Das ursprüngliche Array wird NICHT geändert.

Ich habe es auch gegen Ihr Snippet getaktet und obwohl es nicht schneller ist, ist es schneller als die, mit denen Sie es vergleichen - 21% langsamer 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)

Dadurch wird das Array nicht gedreht. Um diese Frage zu beantworten, müssen Sie ein Array zurückgeben, in dem sich das erste Element am Zähler befindet.
Jean Vincent

0

Ich komme zu spät, aber ich muss diesen guten Antworten einen Stein hinzufügen. Ich wurde gebeten, eine solche Funktion zu codieren, und ich tat es zuerst:

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

Aber es schien weniger effizient zu sein als zu folgen, wenn nes groß ist:

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

Ich bin mir nicht sicher, ob dies der effizienteste Weg ist, aber ich mag die Art und Weise, wie er gelesen wird. Er ist schnell genug für die meisten großen Aufgaben, da ich ihn in der Produktion getestet habe ...

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

Native, schnell, klein, semantisch, arbeitet an alten Motoren und "curryable".

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

0

** Mit der neuesten Version von JS können wir es ganz einfach erstellen **

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

Hier bewegt sich: Anzahl der Umdrehungen , ein Array , das Sie Zufallszahl übergeben können

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

0

ArrayIn JS ist unten eine Methode eingebaut, mit der ein Array ganz einfach gedreht werden kann, und offensichtlich sind diese Methoden von Natur aus unveränderlich .

  • push: Fügt das Element am Ende des Arrays ein.
  • pop: Entfernt das Element vom Ende des Arrays.
  • unshift: Fügt das Element am Anfang des Arrays ein.
  • shift: Entfernt das Element vom Anfang des Arrays.

Die folgende Lösung ( ES6) verwendet zwei Argumente: Das Array muss gedreht werden und n, wie oft das Array gedreht werden soll.

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

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

Es kann zu Array.prototype hinzugefügt und in Ihrer gesamten Anwendung verwendet werden

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

Verwenden der for-Schleife. Hier sind die Schritte

  1. Speichern Sie das erste Element des Arrays als temporäre Variable.
  2. Dann von links nach rechts tauschen.
  3. Weisen Sie dann dem letzten Element des Arrays eine temporäre Variable zu.
  4. Wiederholen Sie diese Schritte für die Anzahl der Umdrehungen.

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



-2

Ich bin mir nicht sicher über die Effizienz, aber ich würde es auf diese nicht mutierende Weise tun:

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


Der erste Teil Ihrer Antwort "Ich bin mir nicht sicher über die Effizienz" lautet also "Ich weiß nicht, ob dies Ihre Frage überhaupt beantwortet". Die ursprüngliche Frage lautet nicht "Was ist ein guter Weg, um ein Array zu drehen?", Sondern speziell die Kompaktheit und Geschwindigkeit (Speicher- bzw. Zeiteffizienz). Die Frage dreht sich alles um Effizienz und Sie haben diese Komponente nicht beantwortet.
Richard Pringle
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.