From 0e4d0b3c2b31430bb216c3eb0ddf892865b2dc89 Mon Sep 17 00:00:00 2001 From: Daniel Kluge Date: Wed, 10 Aug 2022 23:51:24 +0200 Subject: [PATCH] Collections --- diaries/rust/08 - Collections.adoc | 235 +++++++++++++++++++++++++++++ list.json | 3 +- 2 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 diaries/rust/08 - Collections.adoc diff --git a/diaries/rust/08 - Collections.adoc b/diaries/rust/08 - Collections.adoc new file mode 100644 index 0000000..f8472e6 --- /dev/null +++ b/diaries/rust/08 - Collections.adoc @@ -0,0 +1,235 @@ += Standard Collections + +https://doc.rust-lang.org/book/ch08-00-common-collections.html[Link zum Buch] + +== `Vec` - Vektoren + +Vektoren kennt man ja aus C++ als dynamische Alternative zu Arrays. +Es ist quasi eine Linked List, die beliebig erweiterbar bzw. manipulierbar ist. +Wie in der Überschrift zu sehen, sind sie typspezifisch, man kann also nur Daten eines einzigen Typs in diese Liste speichern. + +Wie benutze ich jetzt so einen Vector? +Hier einfach mal eine Übersicht: + +[source, rust] +---- +// -- Erstellen -- +// Mit dem vec!-Pragma +let v = vec![1, 2, 3]; +let v = vec![0; 5]; // 5 Nullen +// Mit new() (mit mut, damit wir gleich etwas zufügen können) +let mut v: Vec = Vec::new(); + +// -- Updaten -- +// Push +v.push(1); +v.push(2); +v.push(3); +v.push(4); +v.push(5); +// -> [1,2,3,4,5] +// Pop, returnt ein Optional +v.pop(3); +v.pop(4); +// -> [1,2,3] +// Insert +v.insert(1, 9); // -> [1,9,2,3] +// Remove +v.remove(1); // -> [1,2,3] + +// -- Lesen -- +// Über Index +let second: &i32 = &v[1]; +// Mit get() (gibt ein Option<&T>) +// Hat den Vorteil, dass es nicht einfach paniced. +match v.get(2) { + Some(value) => {...} + None => (), +} + +// -- Drüber iterieren -- +// mut natürlich nur, wenn wir es verändern wollen +// Wir brauchen hier aber * zum Dereferenzieren! +for i in &mut v { + *i += 50; +} +---- + +=== Achtung, Scope + +Wenn ein Vector aus dem Scope fällt, wird er zusammen mit seinem Inhalt gedropped. +Blöd, wenn man Referenzen auf Elemente aus dem Vector hat. + +=== Ownership + +Wenn `push()` ausgeführt wird, findet ein mutable borrow statt und das kommt mit allen Eigenheiten wie vorher. +Alle Referenzen, die vorher über Index oder `get()` genommen wurden, sind dann ungültig. +Das liegt daran, dass es by `push()` passieren kann, dass neue Speicher reserviert und genutzt werden muss, falls die Elemente nicht mehr nebeneinander passen. + +=== Lifehack: Enum für verschiedene Datentypen + +Ein Vector kann nur einen Datentypen aufnehmen? +Der Datentyp kann aber auch ein Enum sein! + +Also wenn mal ein String neben Zahlen gespeichert werden soll: Einfach einen Enum mit beiden Varianten anlegen. + +=== Weiteres + +Es gibt auch hier Slices und noch eine Menge Tricks. +Die https://doc.rust-lang.org/std/vec/struct.Vec.html[Dokumentation zum Vector] ist da wahrscheinlich sehr hilfreich. + +== Strings + +Strings eine Collection? +Klar, wie in C ja auch. + +Es gibt im Core eigentlich nur `str`, also ein Slice. +Der `String`-Typ kommt aus der Standard-Lib und ist einfacher zu nutzen. + +In den meisten Programmiersprachen kennt man ja `toString()`, hier ist es natürlich `to_string()` und für alle Typen definiert, die den Trait `Display` implementiert haben. +Das gilt zum Beispiel auch für String-Literale, man kann also `str` ganz einfach in einen `String` umwandeln, indem man `"text".to_string()` aufruft. +Natürlich funktioniert auch `String::from("text")`. + +String sind UTF-8 encoded, also egal mit was man sie bewirft, es sollte klappen. +Allerdings ist das Handling deshalb etwas kompliziert. +Rust fasst das ganz gut am Ende der Seite zusammen mit +[quote] +To summarize, strings are complicated. + +Hier wieder eine Übersicht zur Nutzung: + +[source, rust] +---- +// -- Erstellen -- +// String::from() +// "Hello ".to_string() macht das selbe +let mut s = String::from("Hello "); + +// -- Manipulieren -- +// push_str() +// Hier wird kein Ownership übergeben! +// Sollte "world" in einer Variable stehen, ist sie danach weiter nutzbar. +s.push_str("world"); +// -> Hello World +// push(), für einen einzelnen Character +s.push('!'); +// + +// Ist etwas tricky. Der Methodenkopf sieht so aus: +// fn add(self, s: &str) -> String +// Also wird Ownership von s1 übergeben und s2 offensichtlich magisch von &String zu &str. +// Somit ist danach auch s1 nicht mehr gültig. +let s1 = String::from("Hello "); +let s2 = String::from("world!"); +let s3 = s1 + &s2; +// Es geht auch mit noch mehr Elementen! +// Damit das aber nicht zu unübersichtlich wird, gibt es format! +let s1 = String::from("Schere"); +let s2 = String::from("Stein"); +let s3 = String::from("Papier"); +let s4 = format!("{}, {}, {}", s1, s2, s3); +// Hier wird kein Ownership übergeben! +---- + +=== Indexing + +Aus Python z.B. kennt man ja `"Hallo"[0] -> H`. +In Rust geht das nicht. +Das liegt am Aufbau der String, dass sie eben UTF-8 verwenden und `String` eigentlich nur ein `Vec` ist. +Das macht das ganze ordentlich schwierig. + +=== Slicing + +Ist immer eine schlechte Idee, außer man weiß exakt wie lang die einzelnen Zeichen (in Byte) des Strings sind. +Im Englischen ist es normalerweise 1 Byte pro Zeichen, Umlaute sind schon 2, und so weiter. +Sollte man außversehen ein Zeichen "durchschneiden" (also nur 1 Byte eines "ü" im Slice haben), gibt es eine Runtime Panic. + +=== Iterieren + +Über einem String iterieren geht ganz ok. + +[source, rust] +---- +for c in "hallo".chars() { + println!("{}", c); +} +// Ist für europäische Sprachen absolut geeignet. +// Bei Hindi wird es schon wieder eklig. + +for b in "hallo".bytes() { + println!("{}", b); +} +// Wirft eben die einzelnen u8 raus. +---- + +Wenn wir "grapheme" haben wollen (Was anscheinend sowas wie "volle Zeichen" sind, mehr als nur char), gibt es keine eingebaute Funktion aber crates, die das lösen. + +== HashMaps + +Der Erlöser der Programmierer und Lösung jeder Aufgabe bei der Bewerbung, die "O(n)" enthält. +Oder so ähnlich. + +Nutzung: + +[source, rust] +---- +// Das hier ist für die "Abkürzungen" +use std::collections::HashMap; + +// -- Erstellen -- +// iter(), zip() und collect() +// collect() kann in alles mögliche wandeln, deshalb muss der Typ anggeben werden. +let woerter = vec![String::from("eins"), String::from("zwei"), String::from("drei")]; +let zahlen = vec![1, 2, 3]; +let mut zahlwort: HashMap<_, _> = woerter.into_iter().zip(zahlen.into_iter()).collect(); +// Einfach normal +let mut zahlwort = HashMap::new(); + +// -- Nutzung -- +// insert() +// Ownership wird bei den Strings übergeben +zahlwort.insert(String::from("eins"), 1); +zahlwort.insert(String::from("zwei"), 2); +zahlwort.insert(String::from("drei"), 5); +zahlwort.insert(String::from("drei"), 3); // Überschreibt vorheriges +// get() +// Hier wird kein Ownership übergeben +let testwort = String::from("eins"); +let eins_oder_none = zahlwort.get(&testwort); // -> Optional +// entry() +// Checkt, ob etwas da ist und kann im Zweifel etwas einfügen +zahlwort.entry(String::from("vier")).or_insert(4); +// entry kann auch genutzt werden, um den bisherigen Eintrag upzudaten +let bisher = zahlwort.entry(String::from("vier")).or_insert(4); // &mut i32 +*bisher += 1; + +// Drüber Iterieren +for (key, value) in &zahlwort { + println!("{}: {}", key, value); +} +// Sehr selbsterklärend +---- + +=== Ownership + +Falls Key oder Value kein Copy Trait haben, wird der Ownership übergeben. Strings sind also danach ungültig. + +== Hausaufgaben + +Das Buch gibt uns hier ein paar Aufgaben, die wir jetzt lösen können: +* Den Median aus einer Liste finden. Erst sortieren, dann den mittleren Wert. +* Wörter zu "pig-latin" machen. Wenn erster Buchstabe ein Vokal ist, wird "-hay" angehängt, wenn es ein Konsonant ist, wird er ans Ende angefügt (nach "-") und "ay" angehängt. +* Eine kleine Befehlszeile mit Befehlen wie "Add Name to Sales" und Ausgabe. + +Vielleicht werde ich sie irgendwann mal lösen, dann landet der Code hier. + +=== Aufgabe 1 + +[source, rust] +---- +fn main() { + let mut list = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; + list.sort(); + let mid = list.len() / 2; // integer divide + println!("{}", list[mid]); +} +---- diff --git a/list.json b/list.json index 69be3d4..cb1a0f2 100644 --- a/list.json +++ b/list.json @@ -43,7 +43,8 @@ { "title": "04 - Ownership", "filename": "04 - Ownership"}, { "title": "05 - Structs", "filename": "05 - Structs"}, { "title": "06 - Enums", "filename": "06 - Enums"}, - { "title": "07 - Crates & Modules", "filename": "07 - Management"} + { "title": "07 - Crates & Modules", "filename": "07 - Management"}, + { "title": "07 - Collections", "filename": "07 - Collections"} ] }, {