Archive for the ‘set’ Tag
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 -> Germany, en -> England, fr -> France)
Um einen Wert aus einer Map zu erhalten gibt es zwei Möglichkeiten:
scala> m("de")
res144: java.lang.String = Germany
scala> 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> 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.
Hinterlasse einen Kommentar



