frontpage-content/diaries/rust/08 - Collections.adoc
2022-08-10 23:51:24 +02:00

236 lines
7.3 KiB
Plaintext

= Standard Collections
https://doc.rust-lang.org/book/ch08-00-common-collections.html[Link zum Buch]
== `Vec<T>` - 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<i32> = 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<T>
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<u8>` 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]);
}
----