2015-02-05 13 views
6

Wie iterieren Sie über zwei Arrays der gleichen Größe und greifen auf den gleichen Index bei jeder Iteration zu The Scala Way ™?Scala - Iterieren über zwei Arrays

 for ((aListItem, bListItem) <- (aList, bList)) { 
     // do something with items 
     } 

Die Java Weg zur Scala angewendet:

 for(i <- 0 until aList.length) { 
      aList(i) 
      bList(i) 
     } 

Angenommen, beide Listen die gleiche Größe haben.

+0

Was ist los mit dem, was Sie mit fangen? Wenn das Problem ist, dass es nicht kompiliert, fügen Sie einfach Definitionen zum Tupel hinzu: 'for ((aListItem: Int, bListItem: Int) ...' –

+1

Related: http://Stackoverflow.com/q/17199534/406435 – senia

+0

Die Scala Way (TM) verwendet keine Arrays (die änderbar sind), sondern Listen, und diese sind ineffektiv für den wahlfreien Zugriff (dh per Index). Wie auch immer Sie die richtige Lösung verwenden, ist in Rex Kerrs Antwort begraben: '(aList, bList) .zip.foreach {(a, b) => ???}. Und ja, es ist effizient, weil es keine Tupel für jedes Element erzeugen muss (anders als' zip'). –

Antwort

11

tl; dr : Es gibt Kompromisse zwischen sp eed und Bequemlichkeit; Sie müssen Ihren Anwendungsfall kennen, um entsprechend auszuwählen.


Wenn Sie wissen beide Arrays die gleiche Länge haben und Sie brauchen sich keine Sorgen zu machen, wie schnell es ist, die einfachste und kanonische ist zip in einem für Verständnis zu verwenden:

for ((a,b) <- aList zip bList) { ??? } 

Die Methode zip erstellt jedoch ein neues einzelnes Array. Um diesen Aufwand zu vermeiden Sie zipped auf einem Tupel verwenden können, welche die Elemente paarweise Methoden präsentieren wie foreach und map:

(aList, bList).zipped.foreach{ (a,b) => ??? } 

Schneller noch zu Index in den Arrays, vor allem, wenn die Arrays Primitiven wie Int enthalten, da der generische Code oben sie boxen muss.Es ist eine praktische Methode, indices die Sie verwenden können:

for (i <- aList.indices) { ??? } 

Schließlich, wenn Sie so schnell gehen müssen, wie Sie vielleicht können Sie während Schleifen oder Rekursion zurück auf manuelle fallen kann, etwa so:

// While loop 
var i = 0 
while (i < aList.length) { 
    ??? 
    i += 1 
} 

// Recursion 
def loop(i: Int) { 
    if (i < aList.length) { 
    ??? 
    loop(i+1) 
    } 
} 
loop(0) 

wenn Sie etwas Wert sind Rechen, anstatt es eine Nebenwirkung zu sein hat, ist es manchmal mit Rekursion schneller, wenn Sie es entlang passieren:

// Recursion with explicit result 
def loop(i: Int, acc: Int = 0): Int = 
    if (i < aList.length) { 
    val nextAcc = ??? 
    loop(i+1, nextAcc) 
    } 
    else acc 

Da Sie eine Methodendefinition fallen kann i n überall können Sie Rekursion ohne Einschränkung verwenden. Sie können eine @annotation.tailrec Annotation hinzufügen, um sicherzustellen, dass sie zu einer schnellen Schleife mit Sprüngen anstelle einer tatsächlichen Rekursion kompiliert werden kann, die Stapelplatz verbraucht.

public class DotProd { 
    public static int dot(int[] a, int[] b) { 
    int s = 0; 
    for (int i = 0; i < a.length; i++) s += a[i]*b[i]; 
    return s; 
    } 
} 

sowie eine äquivalente Version, wo wir das Skalarprodukt der Längen nehmen:

all diese verschiedenen Ansätze Einen Punkt Produkt auf Länge 1024 Vektoren zu berechnen, können wir diese zu einer Referenz-Implementierung in Java vergleichen Dies ist besonders schlecht von Strings

normalized time 
----------------- 
primitive object method 
--------- ------ --------------------------------- 
100%  100% Java indexed for loop (reference) 
100%  100% Scala while loop 
100%  100% Scala recursion (either way) 
185%  135% Scala for comprehension on indices 
2100%  130% Scala zipped 
3700%  800% Scala zip 

(so können wir Objekte vs. Primitiven beurteilen), natürlich mit Primitiven! (Sie erhalten ähnlich große Zeitsprünge, wenn Sie versuchen, ArrayList s von Integer anstelle von Array von int in Java zu verwenden.) Beachten Sie insbesondere, dass zipped eine recht vernünftige Wahl ist, wenn Sie Objekte gespeichert haben.

Vorsicht vor vorzeitiger Optimierung! Es gibt Vorteile in Bezug auf Klarheit und Sicherheit für funktionelle Formen wie zip. Wenn Sie immer while-Schleifen schreiben, weil Sie denken, dass "jedes kleine Bit hilft", machen Sie wahrscheinlich einen Fehler, weil es mehr Zeit zum Schreiben und Debuggen benötigt und Sie könnten diese Zeit nutzen, um einen wichtigeren Teil Ihres Programms zu optimieren.


Aber vorausgesetzt, Ihre Arrays sind die gleiche Länge ist gefährlich. Sind Sie sicher? Wie viel Aufwand wirst du machen, um sicher zu gehen? Vielleicht solltest du diese Annahme nicht machen?

Wenn Sie es nicht benötigen, um schnell zu sein, nur korrigieren, dann müssen Sie wählen, was zu tun ist, wenn die beiden Arrays nicht die gleiche Länge haben.

Wenn Sie etwas mit allen Elementen bis zur Länge der kürzeren tun wollen, dann zip ist nach wie vor, was Sie verwenden:

// The second is just shorthand for the first 
(aList zip bList).foreach{ case (a,b) => ??? } 
for ((a,b) <- (aList zip bList)) { ??? } 

// This avoids an intermediate array 
(aList, bList).zipped.foreach{ (a,b) => ??? } 

Wenn Sie stattdessen Pad wollen die kürzere mit einem Standardwert Sie

aList.zipAll(bList, aDefault, bDefault).foreach{ case (a,b) => ??? } 
for ((a,b) <- aList.zipAll(bList, aDefault, bDefault)) { ??? } 

In jedem dieser Fälle würde, können Sie yield mit for oder map statt foreach verwenden, um eine Sammlung zu erzeugen.

Wenn Sie den Index für eine Berechnung benötigen oder es wirklich ein Array ist und Sie es wirklich schnell benötigen, müssen Sie die Berechnung manuell durchführen.Fehlende Elemente Klotzen umständlich ist (überlasse ich den Leser als Übung), aber die Grundform wäre:

for (i <- 0 until math.min(aList.length, bList.length)) { ??? } 

, wo man dann i in aList und bList zu Index verwenden.

Wenn Sie wirklich Höchstgeschwindigkeit benötigen würden Sie wieder verwenden (Schwanz) Rekursion oder While-Schleifen:

val n = math.min(aList.length, bList.length) 
var i = 0 
while (i < n) { 
    ??? 
    i += 1 
} 

def loop(i: Int) { 
    if (i < aList.length && i < bList.length) { 
    ??? 
    loop(i+1) 
    } 
} 
loop(0) 
+0

Ausgezeichnete Arbeit, Danke. – BAR

2

Etwas wie:

for ((aListItem, bListItem) <- (aList zip bList)) { 
    // do something with items 
} 

Oder mit map wie:

(aList zip bList).map{ case (alistItem, blistItem) => // do something } 

Aktualisiert:

Für Iterieren ohne ein Zwischenprodukte zu erstellen, können Sie versuchen:

for (i <- 0 until xs.length) ... //xs(i) & ys(i) to access element 

oder einfach

for (i <- xs.indices) ... 
+0

Ich denke th Bei der zusätzlichen Zip-Routine wird die Leistung im Vergleich zur Java-Methode beeinträchtigt. – BAR

+0

Will sehen Quelle zu sehen, ob Zip Wraps oder ein neues Array erstellt. – BAR

+0

@BAR Dies kann in Bezug auf die Leistungsbeurteilung helfen, aber wiederum hängt es vom Anwendungsfall und der Compileroptimierung ab: http://StackOverflow.com/Questions/2794823/is-Scala-Funktions-Programming-Slower-Than- traditionelle Codierung –

1
for { 
    i <- 0 until Math.min(list1.size, list2.size) 
} yield list1(i) + list2(i) 

Oder dass so etwas wie die Grenzen usw.

+0

Es würde nur die Liste1 Grenzen überprüfen. Es könnte immer noch in die Luft gehen, wenn die Listen nicht wirklich in der Größe synchronisiert werden. –

+0

Dies wird _alles_ immer außer Kraft gesetzt, weil du 'to' benutzt hast, was inklusive ist. –

+0

Ich hatte in der Antwort qualifiziert, dass Grenzen überprüft werden müssen, aber ich habe es für Sie aktualisiert. –

1

prüft Ich würde so etwas tun:

aList.indices foreach { i => 
    val (aListItem, bListItem) = (aList(i), bList(i)) 
    // do something with items 
} 
+0

Ich mag das sehr. Präzise mit Scala Syntax und Funktionalität. – BAR

+1

Dies wird eine Ausnahme auslösen, wenn 'bList' kürzer ist als' aList'. –

+0

@RexKerr Beide Listen haben die gleiche Größe. Aktualisierung des Posts zur Klärung – BAR

Verwandte Themen