Skip to content
Snippets Groups Projects
Commit 4fdbb59b authored by Alexander Schultheiß's avatar Alexander Schultheiß
Browse files

Added template

parents
No related merge requests found
/target
/Cargo.lock
/.idea
\ No newline at end of file
[package]
name = "cb-1"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
\ No newline at end of file
# Übungsblatt 1
## Generelles
Die ersten beiden Aufgaben dienen zur Vertiefung Ihrer praktischen Erfahrungen in Rust. Sie implementieren einen Stack und eine Baumstruktur.
## Allgemeine Hinweise
Für diese und alle folgenden Praktikumsaufgaben gilt, dass Einsendungen, die in der jeweils mitgegebenen Testumgebung nicht laufen, mit null Punkten bewertet werden! Das beinhaltet insbesondere alle Programme, die sich nicht fehlerfrei kompilieren lassen. Da Cargo für die Ausführung verantwortlich ist, sollte das Projekt bei Ihnen am Ende mit `cargo test` ohne Fehler und Warnungen durchlaufen.
## Abgabemodus
Die Lösung ist in einem eigenen Git-Repository abzugeben.
Sie können in ihrer Lösung jedoch beliebige Hilfstypen und Module selbst definieren.
Die grundlegende Struktur des hier mitgegebenen Templates sollte jedoch nicht verändert werden.
Insbesondere ist es wichtig, dass die öffentliche Schnittstelle der Library, welche über die Signaturen der Methoden und Funktionen und deren absoluten Pfad definiert wird.
Zur Lösung der Aufgaben steht für Sie dieses Repository mit
- vorgegebenen Modulen [stack](src/stack.rs) und [syntree](src/syntree.rs)
- der vorgegebenen Schnittstelle der Library in [lib](src/lib.rs)
- eine Reihe von Testfällen (Unit-Tests) innerhalb der Module
zur Verfügung.
> Sie können die Implementierung mit `cargo test` prüfen. Mit `cargo test -- --nocapture` werden Konsolenausgaben auch bei korrekten Tests angezeigt.
## Aufgabe 1 (50 Punkte)
### Kurzbeschreibung
Implementieren Sie einen Stack, der beliebig viele Integerzahlen speichern kann ("beliebig viel" bedeutet für uns: lediglich begrenzt durch den Arbeitsspeicher). Erweitern Sie dabei eine vorgegebene Schnittstelle (Stack Trait), sodass Ihr Stack in andere (bereits existierende) Programme eingebunden werden kann.
### Aufgabenstellung
Wie sie in [lib](src/lib.rs) sehen können, betrachten wir einen Stack als eine Datenstruktur, auf der folgende Operationen ausgeführt werden können:
```rust
pub trait Stack {
/// Initialisiere einen leeren Stack
fn init() -> Self;
/// Füge einen neuen Wert zum Stack hinzu
fn push_val(&mut self, i: i32);
/// Lese den obersten Wert, ohne diesen zu entfernen
fn top_val(&self) -> Option<&i32>;
/// Entferne den obersten Wert und gib diesen zurück
fn pop_val(&mut self) -> Option<i32>;
/// Prüfe ob der Stack leer ist
fn is_empty(&self) -> bool;
}
```
- Implementieren Sie den Stack-Trait in [stack.rs](src/stack.rs) für `ListStack` und für `Vec<T>`
#### a) Implementierung für Vec<T> (10 Punkte)
- Implementieren Sie den Trait für Vec<T>
- Da Vec<T> bereits selbst über die Methoden eines Stack verfügt, muss hier lediglich an Vec delegiert werden.
#### b) Implementierung für ListStack (40 Punkte)
- Vervollständigen Sie die Implementierung von Stack für ListStack
- Die Implementierung erfordert den Smart-Pointer `Box<T>`, um den rekursiven Typ sicher zu definieren. Eine Erklärung hierzu finden Sie im [Buch](https://doc.rust-lang.org/book/ch15-01-box.html)
- Zusätzlich wird ein Option<T> benötigt, um Werte mit [take](https://doc.rust-lang.org/std/option/enum.Option.html#method.take) auszutauschen
## Aufgabe 2 (50 Punkte)
### Kurzbeschreibung
Implementieren Sie eine Datenstruktur, die beliebig verzweigte Bäume speichern kann, mit den im Folgenden beschriebenen Methoden.
### Aufgabenstellung
Die hier von Ihnen zu implementierende Datenstruktur dient der Repräsentation eines abstrakten Syntaxbaumes.
```rust
struct SynTree;
impl<'a, T> SynTree<T> {
/// Initialisiert einen neuen Syntaxbaum
pub fn new(value: T, id: ID) -> Syntree<T> { todo!() }
/// Fügt dem angegebenen Elternknoten den mitgegebenen Syntaxbaum als letztes Kind hinzu
pub fn push_node(&mut self, parent_id: ID, new_node: Syntree<T>) -> Result<(), String> { todo!() }
/// Fügt dem angegebenen Elternknoten den mitgegebenen Syntaxbaum als erstes Kind hinzu
pub fn prepend_node(&mut self, parent_id: ID, new_node: Syntree<T>) -> Result<(), String> { todo!() }
/// Fügt dem angegebenen Elternknoten den mitgegebenen Syntaxbaum als Kind an der indizierten Stelle hinzu
pub fn insert_node(
&mut self,
parent_id: ID,
index: usize,
new_node: Syntree<T>,
) -> Result<(), String> { todo!() }
/// Suche nach dem ersten Knoten mit der angegebenen ID und liefere eine mutable Referenz zurück
pub fn seek_node_mut(&'a mut self, id: &ID) -> Option<&'a mut Syntree<T>> { todo!() }
}
```
- Die zu implementierende Datenstruktur Syntree soll Baumknoten in beliebig komplexen Konfigurationen speichern können und davon beliebig viele.
- Vervollständigen Sie die Implementierung in der Datei [syntree.rs](src/syntree.rs).
\ No newline at end of file
mod stack;
mod syntree;
pub trait Stack {
fn init() -> Self;
fn push_val(&mut self, i: i32);
fn top_val(&self) -> Option<&i32>;
fn pop_val(&mut self) -> Option<i32>;
fn is_empty(&self) -> bool;
}
pub use stack::ListStack;
pub use syntree::Syntree;
pub use syntree::ID;
#[cfg(test)]
mod tests {}
use crate::Stack;
// TODO Complete implementation
impl Stack for Vec<i32> {
fn init() -> Self {
todo!()
}
fn push_val(&mut self, i: i32) {
todo!()
}
fn top_val(&self) -> Option<&i32> {
todo!()
}
fn pop_val(&mut self) -> Option<i32> {
todo!()
}
fn is_empty(&self) -> bool {
todo!()
}
}
#[derive(Debug)]
pub enum ListStack {
Val(i32, Option<Box<ListStack>>),
Nil,
}
use ListStack::Nil;
use ListStack::Val;
// Complete implementation of Stack for ListStack
impl Stack for ListStack {
fn init() -> Self {
Nil
}
fn push_val(&mut self, i: i32) {
match self {
Val(value, other) => *self = todo!(),
Nil => *self = todo!(),
};
}
fn top_val(&self) -> Option<&i32> {
todo!()
}
fn pop_val(&mut self) -> Option<i32> {
match self {
Val(value, other) => {
let popped_value = *value;
match other.take() {
None => *self = Nil,
Some(other) => todo!(),
};
todo!()
}
Nil => None,
}
}
fn is_empty(&self) -> bool {
todo!()
}
}
#[cfg(test)]
mod tests {
use crate::stack::ListStack;
use crate::Stack;
use std::fmt::Debug;
use std::thread::sleep;
use std::time::{Duration, Instant};
#[test]
fn fill_and_clear() {
println! {"Testing ListStack"}
fill_and_clear_impl(ListStack::init());
println! {"Testing Vec<T>"}
fill_and_clear_impl(Vec::init());
}
fn fill_and_clear_impl<T: Stack + Debug>(mut stack: T) {
stack.push_val(1);
assert_eq!(stack.top_val(), Some(&1));
stack.push_val(2);
assert_eq!(stack.top_val(), Some(&2));
stack.push_val(-3);
assert_eq!(stack.top_val(), Some(&-3));
println!("{:?}", stack);
let mut comparison = vec![1, 2, -3];
while let Some(val) = stack.pop_val() {
assert_eq!(comparison.pop().unwrap(), val);
}
assert!(stack.is_empty())
}
const BENCHMARK_SIZE: i32 = 10_000_000;
#[test]
fn benchmark() {
let (pushed, popped) = bench(ListStack::init());
println!(
"Own implementation took {}s for push and {} for pop.",
pushed, popped
);
println!("Sleeping for 10 seconds.");
sleep(Duration::from_secs(10));
let (pushed, popped) = bench(Vec::init());
println!(
"Vec wrapper took {}s for push and {} for pop.",
pushed, popped
);
}
fn bench<T: Stack>(mut stack: T) -> (u64, u64) {
let start = Instant::now();
for i in 1..BENCHMARK_SIZE {
stack.push_val(i);
}
println!("Pushed all elements");
let pushed = start.elapsed().as_secs();
while stack.pop_val().is_some() {}
println!("Popped all elements");
let popped = start.elapsed().as_secs();
(pushed, popped)
}
#[test]
fn test_mem() {
let stack = ListStack::init();
mem_test(stack);
println!("Finished memory test for ListStack");
println!("Sleeping for 10 seconds.");
sleep(Duration::from_secs(10));
let stack = Vec::init();
mem_test(stack);
println!("Finished memory test for Vec<T>");
}
fn mem_test<T: Stack>(mut stack: T) {
for i in 1..BENCHMARK_SIZE {
stack.push_val(i);
stack.pop_val();
}
println!("Completed memory test elements");
}
}
use std::fmt::{Display, Formatter};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ID(usize);
#[derive(Debug, PartialEq)]
pub struct Syntree<T> {
id: ID,
value: T,
children: Vec<Syntree<T>>,
}
// Complete the implementation
// Hint: Start with seek_node_mut
impl<'a, T> Syntree<T> {
pub fn new(value: T, id: ID) -> Syntree<T> {
todo!()
}
pub fn push_node(&mut self, parent_id: ID, new_node: Syntree<T>) -> Result<(), String> {
todo!()
}
pub fn prepend_node(&mut self, parent_id: ID, new_node: Syntree<T>) -> Result<(), String> {
todo!()
}
pub fn insert_node(
&mut self,
parent_id: ID,
index: usize,
new_node: Syntree<T>,
) -> Result<(), String> {
todo!()
}
// Anmerkung: `'a` Is ein Lebenszeit angabe für die Referenzen
// Hier wird einfach nur explizit gesagt: Solange `self` lebt, lebt auch die Referenz im Rückgabewert
pub fn seek_node(&'a self, id: &ID) -> Option<&'a Syntree<T>> {
if self.id == *id {
Some(self)
} else {
for child in &self.children {
if let Some(result) = child.seek_node(id) {
return Some(result);
}
}
None
}
}
pub fn seek_node_mut(&'a mut self, id: &ID) -> Option<&'a mut Syntree<T>> {
todo!()
}
}
impl<T: Display> Syntree<T> {
pub fn print(&self) -> String {
if self.children.is_empty() {
format!("{}", self.value)
} else {
format!(
"{}-[{}]",
self.value,
&self
.children
.iter()
.map(|tn| tn.print())
.collect::<Vec<String>>()
.join(",")
)
}
}
}
impl<T: Display> Display for Syntree<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "({})", self.print())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fill_tree() -> Result<(), String> {
let mut tree = Syntree::new(0, ID(0));
for child in 1..3 {
let child_id = ID(child);
let mut child = Syntree::new(child, child_id);
for grandchild in 1..3 {
let id = grandchild * 10;
child.prepend_node(child_id, Syntree::new(id, ID(id)))?;
}
tree.push_node(ID(0), child)?;
}
println!("{}", tree);
assert_eq!(String::from("0-[1-[20,10],2-[20,10]]"), tree.print());
Ok(())
}
#[test]
fn fill_tree_words() -> Result<(), String> {
let mut tree = Syntree::new(to_s("root"), ID(0));
for (child_id, child) in ["first", "second", "third"].iter().map(to_s).enumerate() {
let child_id = ID(child_id);
let mut child = Syntree::new(child, child_id);
if child_id.0 == 0 {
let descendant = Syntree::new(to_s("A"), ID(4));
child.push_node(child_id, descendant)?;
let descendant = Syntree::new(to_s("B"), ID(5));
child.push_node(ID(4), descendant)?;
let descendant = Syntree::new(to_s("C"), ID(6));
child.push_node(ID(5), descendant)?;
}
tree.push_node(ID(0), child)?;
}
println!("{}", tree);
assert_eq!(
String::from("root-[first-[A-[B-[C]]],second,third]"),
tree.print()
);
Ok(())
}
fn to_s<T: Display>(value: T) -> String {
format!("{}", value)
}
#[test]
fn node_id_not_found() -> Result<(), String> {
let mut tree = Syntree::new(0, ID(0));
let child_id = ID(1);
let child = Syntree::new(1, child_id);
tree.push_node(ID(0), child)?;
let child_id = ID(2);
let child = Syntree::new(3, child_id);
assert!(tree.push_node(ID(5), child).is_err());
Ok(())
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment