Teil 6.2: Set, Tuple und Map

Sequenzen von Daten reichen für manche Anwendungsfälle nicht aus. Wenn wir Beispielsweise Daten sortiert halten wollen ist eine Seq nicht die beste Wahl, da das sortierte Einfügen ein Flaschenhals darstellen kann. Das gleiche gilt wenn wir Daten suchen wollen. Die Seq zu durchlaufen und jedes Element zu überprüfen ob es auf unsere Anforderungen passt kann bei vielen Elementen viel zu lange dauern. Aber dafür haben wir ja Sets und Maps, die dafür genau das Richtige sind.

Da beide Collection-Typen vom gleichen Obertyp erben wie Seq können wir auf die gleichen Methoden zurückgreifen um die Datenstrukturen anzusprechen.

Set

Testen wir die Methoden von Set doch gleich einmal:

scala> val set = Set(1, 2, 3)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> set contains 3
res116: Boolean = true

scala> set contains 5
res117: Boolean = false

scala> set ++ List(3, 4, 5)
res118: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)

Wie zu erkennen speichert das Set nur Unikate. Wenn wir also eine Liste aus Daten haben und nur die Anzahl der verschiedenen Elemente haben wollen genügt es diese einfach in ein Set einzufügen und dann die Größe des Sets zu ermitteln:

scala> val xs = List(2, 5, 1, 7, 3, 35, 72, 23, 6, 1, 73, 35, 76, 86, 17)
xs: List[Int] = List(2, 5, 1, 7, 3, 35, 72, 23, 6, 1, 73, 35, 76, 86, 17)

scala> val set = Set() ++ xs
set: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 73, 2, 17, 86, 76, 7, 3, 35, 72, 23)

scala> set.size
res119: Int = 13

Ein weiteres Merkmal ist, dass die Elemente im Set nicht sortiert sind, wie wir an der String-Repräsentation erkennen können. Um dies zu ändern müssen wir anstatt eines Sets einfach ein SortedSet referenzieren:

scala> import scala.collection.immutable.SortedSet
import scala.collection.immutable.SortedSet

scala> val set = SortedSet[Int]() ++ xs
set: scala.collection.immutable.SortedSet[Int] = TreeSet(1, 2, 3, 5, 6, 7, 17, 23, 35, 72, 73, 76, 86)

Es gibt in scala.Predef keine Typreimplementierung von SortedSet weshalb wir es von Hand importieren müssen. Dies erreichen wir ganz einfach mit dem import-Statement. Bitte beachtet, dass wir das SortedSet von Hand typisieren müssen, die die Typinferenz des Compilers hier nicht mehr ausreicht.

Tuple

Der Konstruktor von Map erwartet sogenannte Tuple von Daten. Um also eine Map zu erstellen müssen wir zuerst einen Tuple erstellen. Aber was ist ein Tupel? Ein Tupel ist eine Datenstruktur, die beliebige Daten aufnehmen und mit einander „verbinden“ kann. Die einfachste Möglichkeit einen Tuple zu kreieren ist einfach irgendwelche Daten mit einem Komma voneinander zu trennen und diese dann einzuklammern:

scala> ("de", "Germany")
res125: (java.lang.String, java.lang.String) = (de,Germany)

scala> (1, "hello", 5.34)
res126: (Int, java.lang.String, Double) = (1,hello,5.34)

In Scala können bis zu 22 solcher Daten „vertupelt“ werden. Tuple stellt aber keine Collection dar. Die Methoden, die wir bisher kennen gelernt haben sind größtenteils deshalb auch nicht darauf anzuwenden. Ein Tuple soll auch nicht als Collection dienen. Statt dessen soll er die Möglichkeit bieten mehrere zusammenhänge Daten innerhalb des Codes hin und her zu reichen, ohne dass man sich dafür eine eigene Datenstruktur bauen muss. Brauchen können wir dies bspw. bei einer Methode, die mehrere Daten zurückgeben soll:

def heavyCalculation(): (Int, Int) = {
  val memoryUsage = 30
  val cpuUsage = 91
  (cpuUsage, memoryUsage)
}

Stellen wir uns vor, dass diese Methode große Berechnungen machen müsste. Nachdem die Berechnungen abgeschlossen sind würden wir gerne Wissen wie viel Systemressourcen sie benötigt haben. Wir geben also die Speicher- und CPU-Auslastung in Prozent zurück. Der Rückgabewert wird ebenfalls in Klammern geschrieben um dem Compiler zu verdeutlichen, dass ein Tuple zurückgegeben werden soll.

Tuple haben nur einen kleinen Schönheitsfehler wenn man versucht auf ihre Elemente zuzugreifen:

scala> heavyCalculation()
res130: (Int, Int) = (91,30)

scala> res130._1
res131: Int = 91

scala> res130._2
res132: Int = 30

Da ein Tuple beliebige Daten aufnehmen kann gab es bei ihrer Implementierung keine Möglichkeit einen geeigneten Namen zu definieren  der zu jedem Rückgabewert passt. Man hat sich deshalb auf die Schreibweise tuple._N entschieden wobei N ein Wert zwischen 1 und 22 ist. Je nach Größe des Tuples kann man dann damit an dessen Elemente gelangen. Wird ein Feld adressiert, das bei der momentanen Größe des Tuples nicht existieren kann bekommen wir eine Fehlermeldung:

scala> res130._3
<console>:11: error: value _3 is not a member of (Int, Int)
              res130._3
                     ^

Wenn ein Tuple die Größe 2 hat wird auch von einem Tuple2 gesprochen, bei der Größe 3 von einem Tuple3 usw. Um einen Tuple2 zu erstellen gibt es noch eine spezielle Methode:

scala> 1 -> "hello"
res141: (Int, java.lang.String) = (1,hello)

scala> (1).->("hello")
res142: (Int, java.lang.String) = (1,hello)

Dies ist syntaktischer Zucker für die wohl am meisten erstellte Tuple-Art. Vor allem in Verbindung mit Maps werden wir regen Gebrauch davon machen. Um an alle Elemente eines Tuples zu kommen bieten sich die Method `productIterator` an:

scala> val t = (6, "hello", 3.245, true, 'h')
t: (Int, java.lang.String, Double, Boolean, Char) = (6,hello,3.245,true,h)

scala> val i = t.productIterator
i: Iterator[Any] = non-empty iterator

scala> while (i.hasNext) println(i.next)
6
hello
3.245
true
h

Wir erhalten einen Iterator, über den wir nach Lust und Laune iterieren können.

Map

Maps besitzen die tolle Eigenschaft, dass sie zu einem bestimmten Schlüssel einen Wert abspeichern. Das könnte z.B. so aussehen:

scala> val m = Map("de" -> "Germany", "en" -> "England", "fr" -> "France")
m: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(de -&gt; Germany, en -&gt; England, fr -&gt; France)

Um einen Wert aus einer Map zu erhalten gibt es zwei Möglichkeiten:

scala&gt; m("de")
res144: java.lang.String = Germany

scala&gt; m.get("de")
res145: Option[java.lang.String] = Some(Germany)

Die erste Schreibweise kennen wir bereits. Die Zweite ist auch nicht viel komplizierter gibt aber einen komischen Typ namens Some zurück. Some ist ein Untertyp vom Typ Option. Wofür dieser gut ist sehen wir wenn wir einen Wert anfordern der nicht existiert.

scala&gt; m("ch")
java.util.NoSuchElementException: key not found: ch
<stacktrace>

scala> m.get("ch")
res147: Option[java.lang.String] = None

Im ersten Fall erhalten wir eine Exception, die wir natürlich nicht erhalten wollen. Die Vorgehensweise in Java wäre jetzt zuerst zu prüfen ob der Schlüssel existiert und nur dann seinen Wert anzufordern. Es würde auch die Möglichkeit bestehen einfach die Exception abzufangen und zu verarbeiten. Die Vorgehensweisen haben aber ein Problem: Sie erfordern viel unnützen Code, der uns nicht direkt bei der Bewältigung unserer Aufgabe weiterbringt. Hier eine Beispielimplementierung der ersten Vorgehensweise:

def mapValue[A, B](m: Map[A, B], s: A) =
  if (m contains s) m(s) else null

scala> mapValue(m, "de")
res151: String = Germany

scala> mapValue(m, "ch")
res152: String = null

Die erste Implementierung ist an sich sehr kurz, wir müssen aber null zurückgeben wenn der Wert nicht existiert. Der große Nachteil des Codes ist weiterhin, dass er nicht generisch ist. Würden wir das auch noch einbauen wollen müssten wir nochmal ein bisschen mehr schreiben. Die Implementierung mit Exceptions würde ich gerne auslassen, da sie wohl sowieso niemand praktisch anwendet. Sie würde sowieso mehr Codezeilen beanspruchen.

Scala macht es sich da einfacher und gibt ein Option zurück, das entweder Some oder None sein kann, je nach dem ob ein Wert existiert oder nicht. Was man mit Option so alles machen und wie es uns vor NullPointerExceptions beschützen kann möchte ich nochmal gesondert besprechen, das soll nicht Inhalt dieses Artikels sein.

Um an die Schlüssel oder Werte einer Map zu gelangen werden uns die folgenden Methoden bereitgestellt:

scala> m.keys
res158: Iterable[java.lang.String] = Set(de, en, fr)

scala> m.values
res159: Iterable[java.lang.String] = MapLike(Germany, England, France)

Mehr brauchen wir momentan eigentlich nicht über Map zu wissen.

Advertisements

No comments yet

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: