From: Martin Pitt Date: Sun, 29 Aug 2021 05:54:59 +0000 (+0200) Subject: Move top-level files into concepts/ X-Git-Url: https://piware.de/gitweb/?p=learn-rust.git;a=commitdiff_plain;h=b653e9fe9d8492f2d4b5419721f7e00b392055f9;ds=sidebyside Move top-level files into concepts/ As this now contains multiple projects, it was too messy. --- diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 2ec172e..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "learning" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -global_counter = { version = "0.2.2", default-features = false } diff --git a/concepts/Cargo.toml b/concepts/Cargo.toml new file mode 100644 index 0000000..2ec172e --- /dev/null +++ b/concepts/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "learning" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +global_counter = { version = "0.2.2", default-features = false } diff --git a/concepts/src/lib.rs b/concepts/src/lib.rs new file mode 100644 index 0000000..b7e5eb8 --- /dev/null +++ b/concepts/src/lib.rs @@ -0,0 +1,110 @@ +use std::fs::File; +use std::io::prelude::*; +use std::collections::HashMap; + +pub fn read_file(path: &str) -> Result { + let mut s = String::new(); + File::open(path)? + .read_to_string(&mut s)?; + Ok(s) +} + +// needs Copy trait, good for simple types +pub fn largest(list: &[T]) -> T { + let mut result = list[0]; + for &i in list { + if i > result { + result = i; + } + } + result +} + +// expensive for large strings, don't use that +pub fn largest_clone(list: &[T]) -> T { + let mut result = list[0].clone(); + for i in list { + if *i > result { + result = i.clone(); + } + } + result +} + +// good for everything, but more expensive for simple types +pub fn largest_ref(list: &[T]) -> &T { + let mut result = &list[0]; + for i in list { + if i > result { + result = i; + } + } + result +} + +pub fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} + +/// Wrap and cache an expensive calculation +/// +/// This calls a closure just once for every distinct argument. Any subsequent +/// call to `.value()` with the same argument uses the cached value. +pub struct Cacher +where + T: Fn(A) -> V, + A: Eq + Copy + std::hash::Hash, + V: Copy, +{ + calc: T, + values: HashMap, +} + +impl Cacher +where + T: Fn(A) -> V, + A: Eq + Copy + std::hash::Hash, + V: Copy, +{ + pub fn new(calc: T) -> Cacher { + Cacher { calc, values: HashMap::new() } + } + + pub fn value(&mut self, arg: A) -> V { + match self.values.get(&arg) { + Some(v) => *v, + None => { + let v = (self.calc)(arg); + self.values.insert(arg, v); + v + } + } + } +} + +pub struct Counter5 { + count: u32 +} + +impl Counter5 { + pub fn new() -> Counter5 { + Counter5 { count: 0 } + } +} + +impl Iterator for Counter5 { + type Item = u32; + + fn next(&mut self) -> Option { + if self.count < 5 { + self.count += 1; + Some(self.count) + } else { + None + } + } +} diff --git a/concepts/src/main.rs b/concepts/src/main.rs new file mode 100644 index 0000000..06d122b --- /dev/null +++ b/concepts/src/main.rs @@ -0,0 +1,200 @@ +mod word_utils; +mod lib; + +use std::collections::HashMap; +use std::io::{prelude::*, ErrorKind}; +use std::fs::{self, File}; + +use lib::*; +use word_utils::{first_word, second_word}; + +fn test_strings() { + let s = String::from("Hello world"); + println!("first word: '{}'", first_word(&s)); + println!("second word: '{}'", second_word(&s).unwrap()); + + let s2 = "hello dude blah"; + println!("second word of single: '{}'", second_word(s2).unwrap_or("(none)")); + + match second_word(s2) { + Some(w) => println!("match: second word of '{}' exists: {}", s2, w), + None => println!("match: second word of '{}' does not exist", s2), + } +} + +fn test_vectors() { + let v1 = vec![1, 2, 3]; + println!("statically initialized vector: {:?}", v1); + + let mut v2: Vec = Vec::new(); + v2.push("Hello".to_string()); + v2.push(String::from("world")); + println!("dynamically built vector: {:?}", v2); + println!("first element: {}", v2[0]); + for el in &mut v2 { + *el += "xx"; + } + for el in &v2 { + println!("{}", el); + } +} + +fn test_hashmaps() { + let mut scores = HashMap::new(); + scores.insert("john", 10); + scores.insert("mary", 20); + + println!("scores: {:?}", scores); + + // hash map with .collect() + let persons = vec![("homer", 42), ("marge", 30)]; + let collect_scores: HashMap<_, _> = persons.into_iter().collect(); + println!("collect_scores: {:?}", collect_scores); + + for (p, s) in &collect_scores { + println!("person {}: score {}", p, s); + } + + println!("john's score: {}", scores.get("john").unwrap()); + println!("jake's score: {}", scores.get("jake").unwrap_or(&-1)); + + // double scores + for (_, v) in scores.iter_mut() { + *v *= 2; + } + println!("scores after doubling: {:?}", scores); + + // double scores of immutable hashmap (rebuild it) + let collect_scores: HashMap<_, _> = collect_scores.into_iter() + .map(|(k, v)| (k, 2 * v)) + .collect(); + println!("collect_scores after rebuilding with doubling: {:?}", collect_scores); +} + +fn test_files() { + if let Ok(mut f) = File::open("Cargo.toml") { + let mut contents = String::new(); + match f.read_to_string(&mut contents) { + Ok(len) => println!("successfully opened Cargo.toml: {:?}, contents {} bytes:\n{}\n----------", f, len, contents), + Err(e) => panic!("could not read file: {:?}", e) + } + } else { + println!("could not open Cargo.toml"); + } + + // alternative form, more specific error checking + let mut f = File::open("Cargo.toml").unwrap_or_else(|e| { + if e.kind() == ErrorKind::NotFound { + println!("Cargo.toml not found, falling back to /dev/null"); + // need to return a File + File::open("/dev/null").unwrap() + } else { + panic!("Could not open Cargo.toml: {:?}", e); + } + }); + let mut contents = String::new(); + let len = f.read_to_string(&mut contents).unwrap_or_else(|e| { + panic!("Could not read file: {:?}", e); + }); + println!("successfully opened Cargo.toml with unwrap_or_else: {:?}, contents {} bytes:\n{}\n----------", f, len, contents); + + // using the '?' operator + match read_file("Cargo.toml") { + Ok(s) => println!("Cargo.toml contents:\n{}\n-------------", s), + Err(e) => println!("Could not open Cargo.toml: {:?}", e) + } + + // using std API + match fs::read_to_string("Cargo.toml") { + Ok(s) => println!("Cargo.toml contents:\n{}\n-------------", s), + Err(e) => println!("Could not open Cargo.toml: {:?}", e) + } +} + +fn test_generics() { + let num_list = vec![3, 42, -7, 100, 0]; + println!("largest number: {}", largest(&num_list)); + println!("num_list: {:?}", num_list); + + let char_list = vec!['a', 'y', 'q', 'm']; + println!("largest char: {}", largest(&char_list)); + + let str_list = vec!["hello", "world", "blue", "planet"]; + println!("largest str: {}", largest(&str_list)); + println!("str_list: {:?}", str_list); + + let string_list = vec!["aaaa".to_string(), "xxxxx".to_string(), "ffff".to_string()]; + println!("largest string (with cloning): {}", largest_clone(&string_list)); + println!("largest string (with ref): {}", largest_ref(&string_list)); + println!("string_list: {:?}", string_list); + + let s1 = String::from("abcd"); + let l; + { + let s2 = "efghi"; + l = longest(&s1, s2); + } + println!("longest string: {}", l); +} + +fn test_closures() { + let mut expensive_int_result = Cacher::new(|x| { + println!("calculating expensive int result for {}", x); + 2 * x + }); + + println!("1st int call for value 1: {}", expensive_int_result.value(1)); + println!("2nd int call for value 1: {}", expensive_int_result.value(1)); + println!("1st int call for value 2: {}", expensive_int_result.value(2)); + + let mut expensive_str_result = Cacher::new(|x: &str| { + println!("calculating expensive str result for {}", x); + x.len() + }); + + println!("1st int call for value abc: {}", expensive_str_result.value("abc")); + println!("2nd int call for value abc: {}", expensive_str_result.value("abc")); + println!("1st int call for value defg: {}", expensive_str_result.value("defg")); +} + +fn test_iterators() { + let v1 = vec!["Hello", "good", "world"]; + // implied default is .into_iter() which consumes v1 + for s in v1.iter() { + println!("element: {}", s); + } + // can still use it now + println!("v1: {:?}", &v1); + + // manual iteration + let mut v1_iter = v1.iter(); + while let Some(e) = v1_iter.next() { + println!("while loop over iter: {}", e); + } + + for l_element in v1.iter().filter(|x| x.contains('l')) { + println!("v1 element containing 'l': {}", l_element); + } + + let v2 = vec![1, 2, 3]; + let double_v2: Vec<_> = v2.iter().map(|x| x * 2).collect(); + println!("doubled v2: {:?}", double_v2); + + for i in v2.into_iter().map(|x| x * 3) { + println!("for loop triplicating v2: {}", i); + } + + for i in Counter5::new() { + println!("Counter 5 value: {}", i); + } +} + +fn main() { + test_strings(); + test_vectors(); + test_hashmaps(); + test_files(); + test_generics(); + test_closures(); + test_iterators(); +} diff --git a/concepts/src/word_utils.rs b/concepts/src/word_utils.rs new file mode 100644 index 0000000..e3e71bf --- /dev/null +++ b/concepts/src/word_utils.rs @@ -0,0 +1,43 @@ +pub fn first_word(s: &str) -> &str { + for (i, &item) in s.as_bytes().iter().enumerate() { + if item == b' ' { + return &s[..i]; + } + } + + s +} + +pub fn second_word(s: &str) -> Option<&str> { + for (i, &item) in s.as_bytes().iter().enumerate() { + if item == b' ' { + return Some(first_word(&s[(i + 1)..])); + } + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_first_word() { + assert_eq!(first_word(""), ""); + assert_eq!(first_word("one"), "one"); + assert_eq!(first_word("one two"), "one"); + + assert_eq!(first_word(&String::from("one two")), "one"); + } + + #[test] + fn test_second_word() { + assert_eq!(second_word(""), None); + assert_eq!(second_word("one"), None); + assert_eq!(second_word("one two"), Some("two")); + assert_eq!(second_word("one two three"), Some("two")); + + assert_eq!(second_word(&String::from("one two three")), Some("two")); + } +} diff --git a/concepts/tests/test_lib.rs b/concepts/tests/test_lib.rs new file mode 100644 index 0000000..af9bbe2 --- /dev/null +++ b/concepts/tests/test_lib.rs @@ -0,0 +1,51 @@ +#[macro_use] +extern crate global_counter; + +use learning::*; + +#[test] +fn test_longest() { + assert_eq!(longest("abc", "de"), "abc"); + assert_eq!(longest("abc", "def"), "def"); + assert_eq!(longest("abc", "defg"), "defg"); +} + +#[test] +fn test_cacher_int_int() { + global_default_counter!(CALLED, u32); + let mut cacher = Cacher::new(|x| { + CALLED.inc(); + 2 * x + }); + assert_eq!(cacher.value(1), 2); + assert_eq!(CALLED.get_cloned(), 1); + // second time cached + assert_eq!(cacher.value(1), 2); + assert_eq!(CALLED.get_cloned(), 1); + // re-evaluated for new value + assert_eq!(cacher.value(-2), -4); + assert_eq!(CALLED.get_cloned(), 2); + // old arg still cached + assert_eq!(cacher.value(1), 2); + assert_eq!(CALLED.get_cloned(), 2); +} + +#[test] +fn test_cacher_str_usize() { + global_default_counter!(CALLED, u32); + let mut cacher = Cacher::new(|x: &str| { + CALLED.inc(); + x.len() + }); + assert_eq!(cacher.value("abc"), 3); + assert_eq!(CALLED.get_cloned(), 1); + // second time cached + assert_eq!(cacher.value("abc"), 3); + assert_eq!(CALLED.get_cloned(), 1); + // re-evaluated for new value + assert_eq!(cacher.value("defg"), 4); + assert_eq!(CALLED.get_cloned(), 2); + // old arg still cached + assert_eq!(cacher.value("abc"), 3); + assert_eq!(CALLED.get_cloned(), 2); +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index b7e5eb8..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::fs::File; -use std::io::prelude::*; -use std::collections::HashMap; - -pub fn read_file(path: &str) -> Result { - let mut s = String::new(); - File::open(path)? - .read_to_string(&mut s)?; - Ok(s) -} - -// needs Copy trait, good for simple types -pub fn largest(list: &[T]) -> T { - let mut result = list[0]; - for &i in list { - if i > result { - result = i; - } - } - result -} - -// expensive for large strings, don't use that -pub fn largest_clone(list: &[T]) -> T { - let mut result = list[0].clone(); - for i in list { - if *i > result { - result = i.clone(); - } - } - result -} - -// good for everything, but more expensive for simple types -pub fn largest_ref(list: &[T]) -> &T { - let mut result = &list[0]; - for i in list { - if i > result { - result = i; - } - } - result -} - -pub fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { - if x.len() > y.len() { - x - } else { - y - } -} - -/// Wrap and cache an expensive calculation -/// -/// This calls a closure just once for every distinct argument. Any subsequent -/// call to `.value()` with the same argument uses the cached value. -pub struct Cacher -where - T: Fn(A) -> V, - A: Eq + Copy + std::hash::Hash, - V: Copy, -{ - calc: T, - values: HashMap, -} - -impl Cacher -where - T: Fn(A) -> V, - A: Eq + Copy + std::hash::Hash, - V: Copy, -{ - pub fn new(calc: T) -> Cacher { - Cacher { calc, values: HashMap::new() } - } - - pub fn value(&mut self, arg: A) -> V { - match self.values.get(&arg) { - Some(v) => *v, - None => { - let v = (self.calc)(arg); - self.values.insert(arg, v); - v - } - } - } -} - -pub struct Counter5 { - count: u32 -} - -impl Counter5 { - pub fn new() -> Counter5 { - Counter5 { count: 0 } - } -} - -impl Iterator for Counter5 { - type Item = u32; - - fn next(&mut self) -> Option { - if self.count < 5 { - self.count += 1; - Some(self.count) - } else { - None - } - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 06d122b..0000000 --- a/src/main.rs +++ /dev/null @@ -1,200 +0,0 @@ -mod word_utils; -mod lib; - -use std::collections::HashMap; -use std::io::{prelude::*, ErrorKind}; -use std::fs::{self, File}; - -use lib::*; -use word_utils::{first_word, second_word}; - -fn test_strings() { - let s = String::from("Hello world"); - println!("first word: '{}'", first_word(&s)); - println!("second word: '{}'", second_word(&s).unwrap()); - - let s2 = "hello dude blah"; - println!("second word of single: '{}'", second_word(s2).unwrap_or("(none)")); - - match second_word(s2) { - Some(w) => println!("match: second word of '{}' exists: {}", s2, w), - None => println!("match: second word of '{}' does not exist", s2), - } -} - -fn test_vectors() { - let v1 = vec![1, 2, 3]; - println!("statically initialized vector: {:?}", v1); - - let mut v2: Vec = Vec::new(); - v2.push("Hello".to_string()); - v2.push(String::from("world")); - println!("dynamically built vector: {:?}", v2); - println!("first element: {}", v2[0]); - for el in &mut v2 { - *el += "xx"; - } - for el in &v2 { - println!("{}", el); - } -} - -fn test_hashmaps() { - let mut scores = HashMap::new(); - scores.insert("john", 10); - scores.insert("mary", 20); - - println!("scores: {:?}", scores); - - // hash map with .collect() - let persons = vec![("homer", 42), ("marge", 30)]; - let collect_scores: HashMap<_, _> = persons.into_iter().collect(); - println!("collect_scores: {:?}", collect_scores); - - for (p, s) in &collect_scores { - println!("person {}: score {}", p, s); - } - - println!("john's score: {}", scores.get("john").unwrap()); - println!("jake's score: {}", scores.get("jake").unwrap_or(&-1)); - - // double scores - for (_, v) in scores.iter_mut() { - *v *= 2; - } - println!("scores after doubling: {:?}", scores); - - // double scores of immutable hashmap (rebuild it) - let collect_scores: HashMap<_, _> = collect_scores.into_iter() - .map(|(k, v)| (k, 2 * v)) - .collect(); - println!("collect_scores after rebuilding with doubling: {:?}", collect_scores); -} - -fn test_files() { - if let Ok(mut f) = File::open("Cargo.toml") { - let mut contents = String::new(); - match f.read_to_string(&mut contents) { - Ok(len) => println!("successfully opened Cargo.toml: {:?}, contents {} bytes:\n{}\n----------", f, len, contents), - Err(e) => panic!("could not read file: {:?}", e) - } - } else { - println!("could not open Cargo.toml"); - } - - // alternative form, more specific error checking - let mut f = File::open("Cargo.toml").unwrap_or_else(|e| { - if e.kind() == ErrorKind::NotFound { - println!("Cargo.toml not found, falling back to /dev/null"); - // need to return a File - File::open("/dev/null").unwrap() - } else { - panic!("Could not open Cargo.toml: {:?}", e); - } - }); - let mut contents = String::new(); - let len = f.read_to_string(&mut contents).unwrap_or_else(|e| { - panic!("Could not read file: {:?}", e); - }); - println!("successfully opened Cargo.toml with unwrap_or_else: {:?}, contents {} bytes:\n{}\n----------", f, len, contents); - - // using the '?' operator - match read_file("Cargo.toml") { - Ok(s) => println!("Cargo.toml contents:\n{}\n-------------", s), - Err(e) => println!("Could not open Cargo.toml: {:?}", e) - } - - // using std API - match fs::read_to_string("Cargo.toml") { - Ok(s) => println!("Cargo.toml contents:\n{}\n-------------", s), - Err(e) => println!("Could not open Cargo.toml: {:?}", e) - } -} - -fn test_generics() { - let num_list = vec![3, 42, -7, 100, 0]; - println!("largest number: {}", largest(&num_list)); - println!("num_list: {:?}", num_list); - - let char_list = vec!['a', 'y', 'q', 'm']; - println!("largest char: {}", largest(&char_list)); - - let str_list = vec!["hello", "world", "blue", "planet"]; - println!("largest str: {}", largest(&str_list)); - println!("str_list: {:?}", str_list); - - let string_list = vec!["aaaa".to_string(), "xxxxx".to_string(), "ffff".to_string()]; - println!("largest string (with cloning): {}", largest_clone(&string_list)); - println!("largest string (with ref): {}", largest_ref(&string_list)); - println!("string_list: {:?}", string_list); - - let s1 = String::from("abcd"); - let l; - { - let s2 = "efghi"; - l = longest(&s1, s2); - } - println!("longest string: {}", l); -} - -fn test_closures() { - let mut expensive_int_result = Cacher::new(|x| { - println!("calculating expensive int result for {}", x); - 2 * x - }); - - println!("1st int call for value 1: {}", expensive_int_result.value(1)); - println!("2nd int call for value 1: {}", expensive_int_result.value(1)); - println!("1st int call for value 2: {}", expensive_int_result.value(2)); - - let mut expensive_str_result = Cacher::new(|x: &str| { - println!("calculating expensive str result for {}", x); - x.len() - }); - - println!("1st int call for value abc: {}", expensive_str_result.value("abc")); - println!("2nd int call for value abc: {}", expensive_str_result.value("abc")); - println!("1st int call for value defg: {}", expensive_str_result.value("defg")); -} - -fn test_iterators() { - let v1 = vec!["Hello", "good", "world"]; - // implied default is .into_iter() which consumes v1 - for s in v1.iter() { - println!("element: {}", s); - } - // can still use it now - println!("v1: {:?}", &v1); - - // manual iteration - let mut v1_iter = v1.iter(); - while let Some(e) = v1_iter.next() { - println!("while loop over iter: {}", e); - } - - for l_element in v1.iter().filter(|x| x.contains('l')) { - println!("v1 element containing 'l': {}", l_element); - } - - let v2 = vec![1, 2, 3]; - let double_v2: Vec<_> = v2.iter().map(|x| x * 2).collect(); - println!("doubled v2: {:?}", double_v2); - - for i in v2.into_iter().map(|x| x * 3) { - println!("for loop triplicating v2: {}", i); - } - - for i in Counter5::new() { - println!("Counter 5 value: {}", i); - } -} - -fn main() { - test_strings(); - test_vectors(); - test_hashmaps(); - test_files(); - test_generics(); - test_closures(); - test_iterators(); -} diff --git a/src/word_utils.rs b/src/word_utils.rs deleted file mode 100644 index e3e71bf..0000000 --- a/src/word_utils.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub fn first_word(s: &str) -> &str { - for (i, &item) in s.as_bytes().iter().enumerate() { - if item == b' ' { - return &s[..i]; - } - } - - s -} - -pub fn second_word(s: &str) -> Option<&str> { - for (i, &item) in s.as_bytes().iter().enumerate() { - if item == b' ' { - return Some(first_word(&s[(i + 1)..])); - } - } - - None -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_first_word() { - assert_eq!(first_word(""), ""); - assert_eq!(first_word("one"), "one"); - assert_eq!(first_word("one two"), "one"); - - assert_eq!(first_word(&String::from("one two")), "one"); - } - - #[test] - fn test_second_word() { - assert_eq!(second_word(""), None); - assert_eq!(second_word("one"), None); - assert_eq!(second_word("one two"), Some("two")); - assert_eq!(second_word("one two three"), Some("two")); - - assert_eq!(second_word(&String::from("one two three")), Some("two")); - } -} diff --git a/tests/test_lib.rs b/tests/test_lib.rs deleted file mode 100644 index af9bbe2..0000000 --- a/tests/test_lib.rs +++ /dev/null @@ -1,51 +0,0 @@ -#[macro_use] -extern crate global_counter; - -use learning::*; - -#[test] -fn test_longest() { - assert_eq!(longest("abc", "de"), "abc"); - assert_eq!(longest("abc", "def"), "def"); - assert_eq!(longest("abc", "defg"), "defg"); -} - -#[test] -fn test_cacher_int_int() { - global_default_counter!(CALLED, u32); - let mut cacher = Cacher::new(|x| { - CALLED.inc(); - 2 * x - }); - assert_eq!(cacher.value(1), 2); - assert_eq!(CALLED.get_cloned(), 1); - // second time cached - assert_eq!(cacher.value(1), 2); - assert_eq!(CALLED.get_cloned(), 1); - // re-evaluated for new value - assert_eq!(cacher.value(-2), -4); - assert_eq!(CALLED.get_cloned(), 2); - // old arg still cached - assert_eq!(cacher.value(1), 2); - assert_eq!(CALLED.get_cloned(), 2); -} - -#[test] -fn test_cacher_str_usize() { - global_default_counter!(CALLED, u32); - let mut cacher = Cacher::new(|x: &str| { - CALLED.inc(); - x.len() - }); - assert_eq!(cacher.value("abc"), 3); - assert_eq!(CALLED.get_cloned(), 1); - // second time cached - assert_eq!(cacher.value("abc"), 3); - assert_eq!(CALLED.get_cloned(), 1); - // re-evaluated for new value - assert_eq!(cacher.value("defg"), 4); - assert_eq!(CALLED.get_cloned(), 2); - // old arg still cached - assert_eq!(cacher.value("abc"), 3); - assert_eq!(CALLED.get_cloned(), 2); -}