Praxisteil & Übungen: Collections (Datenstrukturen)
Dieser Praxisteil führt Sie Schritt für Schritt durch die Verwaltung von dynamischen Datenstrukturen in Rust. Sie arbeiten mit Vektoren und HashMaps.
1. Praxis-Szenario: Die Verwaltung eines E-Commerce-Warenkorbs und Lagers
Sie entwickeln das Backend für einen Online-Shop. Sie müssen zwei zentrale Aufgaben lösen:
- Den Warenkorb eines Kunden verwalten. Dieser speichert die Namen der ausgewählten Artikel in einer Liste.
- Das Lager verwalten. Dieses ordnet jedem Artikelnamen die aktuelle Stückzahl zu.
Die Übungsaufgabe befindet sich im Verzeichnis:
- exercises/04_collections/src/main.rs (Starten Sie hier und vervollständigen Sie die vorbereiteten Funktionen)
2. Strukturierte Praxis-Einheiten
2.1 Get Started: Artikel zum Warenkorb hinzufügen (Vec<T>)
Der Vektor (Vec<T>) ist eine dynamische Liste auf dem Heap. Mit der Methode push() hängen Sie Elemente hinten an.
Beispiel:
#![allow(unused)]
fn main() {
let mut liste = Vec::new();
liste.push(String::from("Apfel"));
}
Erklärung:
- Vec::new(): Erstellt einen leeren Vektor.
- push(): Fügt ein Element am Ende hinzu. Der Vektor fordert bei Bedarf automatisch mehr Speicher an.
Aufgabe:
Schreiben Sie eine Funktion hinzufuegen, die eine veränderliche Referenz auf einen String-Vektor (&mut Vec<String>) und einen Artikelnamen (&str) entgegennimmt. Fügen Sie den Artikel als String zum Vektor hinzu.
2.2 Artikel aus dem Warenkorb entfernen
Um ein bestimmtes Element aus einem Vektor zu entfernen, müssen Sie dessen Position finden. Die Methode position() auf einem Iterator liefert den ersten passenden Index. Mit remove() löschen Sie das Element an diesem Index.
Beispiel:
#![allow(unused)]
fn main() {
let mut liste = vec!["A", "B", "A"];
if let Some(pos) = liste.iter().position(|&x| x == "A") {
liste.remove(pos);
}
}
Erklärung:
- iter(): Erzeugt einen Iterator über die Elemente.
- position(): Sucht von links nach rechts das erste Element, auf das die Bedingung zutrifft. Gibt ein
Option<usize>zurück. - remove(): Entfernt das Element am Index. Verschiebt alle nachfolgenden Elemente nach links.
Aufgabe:
Schreiben Sie eine Funktion entfernen, die eine veränderliche Referenz auf einen String-Vektor (&mut Vec<String>) und einen Artikelnamen (&str) entgegennimmt. Suchen Sie die Position des ersten Vorkommens und entfernen Sie es, falls es existiert.
2.3 Lagerbestand aktualisieren mit der Entry-API (HashMap<K, V>)
Die HashMap speichert Schlüssel-Wert-Paare. Die Entry-API (entry()) in Kombination mit or_insert() ist der effizienteste Weg, Werte einzufügen oder zu aktualisieren.
Beispiel:
#![allow(unused)]
fn main() {
let mut map = HashMap::new();
let counter = map.entry("Apfel").or_insert(0);
*counter += 1;
}
Erklärung:
- entry(): Prüft, ob der Schlüssel bereits in der Map existiert.
- or_insert(): Fügt den Standardwert ein, falls der Schlüssel fehlt. Gibt eine veränderliche Referenz (
&mut V) auf den Wert zurück. - *: Der Dereferenzierungs-Operator greift auf den Wert hinter der Referenz zu.
Aufgabe:
Schreiben Sie eine Funktion bestand_hinzufuegen, die eine veränderliche Referenz auf ein Lager (&mut HashMap<String, u32>), einen Artikelnamen (&str) und eine Menge (u32) entgegennimmt. Erhöhen Sie den Bestand des Artikels um den angegebenen Wert. Nutzen Sie zwingend die Entry-API.
2.4 Verfügbarkeit prüfen
Sie können über die Methode get() prüfen, ob ein Schlüssel in der Map existiert.
Beispiel:
#![allow(unused)]
fn main() {
let menge = map.get("Apfel");
}
Erklärung:
- get(): Liefert eine
Option<&V>zurück. Existiert der Schlüssel nicht, erhalten SieNone.
Aufgabe:
Schreiben Sie eine Funktion ist_verfuegbar, die eine unveränderliche Referenz auf das Lager (&HashMap<String, u32>) und einen Artikelnamen (&str) entgegennimmt. Die Funktion gibt true zurück, wenn der Artikel existiert und seine Menge größer als 0 ist.
3. Genaue Code-Erklärung der Musterlösung
Der fertige Code der Musterlösung befindet sich unter solutions/04_collections/src/main.rs:
1: // Musterlösung zu Übung 4: Collections (Vektoren & HashMaps)
2: // Alle Anforderungen wurden erfolgreich implementiert.
3:
4: use std::collections::HashMap;
5:
6: fn main() {
7: let mut warenkorb = Vec::new();
8: let mut lager = HashMap::new();
9:
10: // Lagerbestand initialisieren
11: bestand_hinzufuegen(&mut lager, "Apfel", 10);
12: bestand_hinzufuegen(&mut lager, "Banane", 5);
13:
14: println!("Lagerbestand:");
15: println!("Apfel verfügbar? {}", ist_verfuegbar(&lager, "Apfel"));
16: println!("Birne verfügbar? {}", ist_verfuegbar(&lager, "Birne"));
17:
18: // Artikel zum Warenkorb hinzufügen
19: hinzufuegen(&mut warenkorb, "Apfel");
20: hinzufuegen(&mut warenkorb, "Banane");
21: hinzufuegen(&mut warenkorb, "Apfel");
22:
23: println!("\nWarenkorb nach Hinzufügen: {:?}", warenkorb);
24:
25: // Einen Apfel entfernen
26: entfernen(&mut warenkorb, "Apfel");
27: println!("Warenkorb nach Entfernen von einem Apfel: {:?}", warenkorb);
28: }
29:
30: // 1. Artikel zum Warenkorb hinzufügen
31: fn hinzufuegen(warenkorb: &mut Vec<String>, artikel: &str) {
32: warenkorb.push(artikel.to_string());
33: }
34:
35: // 2. Ersten passenden Artikel aus dem Warenkorb entfernen
36: fn entfernen(warenkorb: &mut Vec<String>, artikel: &str) {
37: if let Some(pos) = warenkorb.iter().position(|x| x == artikel) {
38: warenkorb.remove(pos);
39: }
40: }
41:
42: // 3. Bestand in der HashMap erhöhen (Entry-API)
43: fn bestand_hinzufuegen(lager: &mut HashMap<String, u32>, artikel: &str, menge: u32) {
44: let eintrag = lager.entry(artikel.to_string()).or_insert(0);
45: *eintrag += menge;
46: }
47:
48: // 4. Verfügbarkeit prüfen (Bestand > 0)
49: fn ist_verfuegbar(lager: &HashMap<String, u32>, artikel: &str) -> bool {
50: if let Some(&menge) = lager.get(artikel) {
51: menge > 0
52: } else {
53: false
54: }
55: }
Zeilen-Analyse der Lösung:
- Zeile 4:
use std::collections::HashMap;– Importiert dieHashMapaus der Standardbibliothek in den aktuellen Namensraum. - Zeile 7:
let mut warenkorb = Vec::new();– Erstellt einen leeren, veränderlichen Vektor. Rust erkennt später automatisch den TypVec<String>. - Zeile 8:
let mut lager = HashMap::new();– Erstellt eine leere, veränderliche HashMap auf dem Heap. - Zeile 11-12: Ruft
bestand_hinzufuegenauf, um Äpfel und Bananen im Lager zu registrieren. - Zeile 15-16: Ruft
ist_verfuegbarauf und gibt das Ergebnis aus.Apfellieferttrue,Birneliefertfalse. - Zeile 19-21: Fügt dem Warenkorb zwei Äpfel und eine Banane hinzu.
- Zeile 26: Ruft
entfernenauf. Der erste gefundene Apfel wird aus dem Warenkorb gelöscht. - Zeile 31:
fn hinzufuegen(warenkorb: &mut Vec<String>, artikel: &str)– Nimmt den Vektor als veränderliche Referenz entgegen. Der Artikel wird als ausgelesener String-Slice übergeben. - Zeile 32:
warenkorb.push(artikel.to_string());– Konvertiert den Slice&strin ein eigenständigesString-Objekt auf dem Heap. Danach wird das Objekt an den Vektor angehängt. - Zeile 36:
fn entfernen(warenkorb: &mut Vec<String>, artikel: &str)– Deklariert die Funktion zum Entfernen eines Elements aus der Liste. - Zeile 37:
if let Some(pos) = warenkorb.iter().position(|x| x == artikel)– Erstellt einen Iterator.positionsucht nach dem ersten Element, das dem Artikel entspricht. Wenn gefunden, wird der Index inposgebunden. - Zeile 38:
warenkorb.remove(pos);– Entfernt das Element am Indexpos. Der Speicherplatz wird freigegeben. Die Nachfolgerelemente rücken nach. - Zeile 43:
fn bestand_hinzufuegen(...)– Deklariert die Funktion zur Bestandsänderung. - Zeile 44:
let eintrag = lager.entry(artikel.to_string()).or_insert(0);– Sucht den Artikel in der Map. Existiert er nicht, wird er mit dem Wert0neu angelegt. Die Methode gibt eine veränderliche Referenz (&mut u32) auf den Wert in der Map zurück. - Zeile 45:
*eintrag += menge;– Dereferenziert den Zeiger. Danach addiert die Zeile die neue Menge direkt auf den Wert im Heap-Speicher. - Zeile 49:
fn ist_verfuegbar(...)– Deklariert die Funktion zur Prüfung der Verfügbarkeit. - Zeile 50:
if let Some(&menge) = lager.get(artikel)– Sucht nach dem Artikel. Dageteine Referenz auf den Wert zurückgibt (Option<&u32>), destrukuriert&mengeden Zeiger. So erhalten Sie direkt den kopierten Wert vonmenge. - Zeile 51:
menge > 0– Gibttruezurück, wenn die Stückzahl größer als 0 ist. - Zeile 53:
false– Standard-Rückgabe. Wird ausgeführt, falls der Artikel gar nicht im Lager-Verzeichnis existiert.