From bcf12217d51c79bb5c4e8cd844e33f431bacdd89 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 5 Feb 2025 12:14:09 +0000 Subject: [PATCH 01/26] fix(unwrap_or_expect) --- solutions/unwrap_or_expect/src/lib.rs | 22 ++++++++------- tests/unwrap_or_expect_test/src/main.rs | 36 ++++++++++++++++--------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/solutions/unwrap_or_expect/src/lib.rs b/solutions/unwrap_or_expect/src/lib.rs index 93575c4c..b6cb0d58 100644 --- a/solutions/unwrap_or_expect/src/lib.rs +++ b/solutions/unwrap_or_expect/src/lib.rs @@ -1,17 +1,19 @@ pub enum Security { Unknown, - High, - Medium, - Low, - BlockServer, + Message, + Warning, + NotFound, + UnexpectedUrl, } -pub fn fetch_data(server: Result, security_level: Security) -> String { +pub fn fetch_data(server: Result<&str, &str>, security_level: Security) -> String { match security_level { - Security::Unknown => server.unwrap(), - Security::High => server.expect("ERROR: program stops"), - Security::Medium => server.unwrap_or("WARNING: check the server".to_string()), - Security::Low => server.unwrap_or_else(|url| "Not found: ".to_string() + url.as_str()), - Security::BlockServer => server.unwrap_err(), + Security::Unknown => server.unwrap().to_owned(), + Security::Message => server.expect("ERROR: program stops").to_owned(), + Security::Warning => server.unwrap_or("WARNING: check the server").to_owned(), + Security::NotFound => server + .map(String::from) + .unwrap_or_else(|url| format!("Not found: {}", url)), + Security::UnexpectedUrl => server.unwrap_err().to_owned(), } } diff --git a/tests/unwrap_or_expect_test/src/main.rs b/tests/unwrap_or_expect_test/src/main.rs index 7efc7f67..75900e9f 100644 --- a/tests/unwrap_or_expect_test/src/main.rs +++ b/tests/unwrap_or_expect_test/src/main.rs @@ -1,6 +1,19 @@ use unwrap_or_expect::*; -fn main() {} +fn main() { + println!("{}", fetch_data(Ok("server1.com"), Security::Warning)); + println!("{}", fetch_data(Err("server.com"), Security::Warning)); + println!("{}", fetch_data(Err("server2.com"), Security::NotFound)); + + // Panics with no custom message + // fetch_data(Err("ERROR CRITICAL"), Security::Unknown); + + // Panics with the message "ERROR: program stops" + // fetch_data(Err("server.com"), Security::Message); + + // Panics with the message "malicious_server.com" + // fetch_data(Ok("malicious_server.com"), Security::UnexpectedUrl); +} #[cfg(test)] mod tests { @@ -9,51 +22,48 @@ mod tests { #[test] #[should_panic(expected = "ERROR: program stops")] fn test_expect() { - fetch_data(Err(String::new()), Security::High); + fetch_data(Err(""), Security::Message); } #[test] #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: \"ERROR CRITICAL\"")] fn test_unwrap() { - fetch_data(Err("ERROR CRITICAL".to_string()), Security::Unknown); + fetch_data(Err("ERROR CRITICAL"), Security::Unknown); } #[test] #[should_panic(expected = "malicious_server.com")] fn test_unwrap_err() { - fetch_data( - Ok("malicious_server.com".to_string()), - Security::BlockServer, - ); + fetch_data(Ok("malicious_server.com"), Security::UnexpectedUrl); } #[test] fn test_unwrap_or() { assert_eq!( - fetch_data(Err(String::new()), Security::Medium), + fetch_data(Err(""), Security::Warning), "WARNING: check the server".to_string() ); } #[test] fn test_unwrap_or_else() { assert_eq!( - fetch_data(Err("another_server.com".to_string()), Security::Low), + fetch_data(Err("another_server.com"), Security::NotFound), "Not found: another_server.com".to_string() ); } #[test] fn test_ok() { assert_eq!( - fetch_data(Ok("server.com".to_string()), Security::Low), + fetch_data(Ok("server.com"), Security::Message), "server.com" ); assert_eq!( - fetch_data(Ok("server.com".to_string()), Security::Medium), + fetch_data(Ok("server.com"), Security::Warning), "server.com" ); assert_eq!( - fetch_data(Ok("server.com".to_string()), Security::High), + fetch_data(Ok("server.com"), Security::NotFound), "server.com" ); assert_eq!( - fetch_data(Ok("server.com".to_string()), Security::Unknown), + fetch_data(Ok("server.com"), Security::Unknown), "server.com" ); } From fa944440914e5643e1cd9537b127311a52cb9d0a Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 5 Feb 2025 12:27:16 +0000 Subject: [PATCH 02/26] fix(panic) --- solutions/panic/src/lib.rs | 37 +----------------------------------- tests/panic_test/src/lib.rs | 16 ++++++++++++++++ tests/panic_test/src/main.rs | 29 ---------------------------- 3 files changed, 17 insertions(+), 65 deletions(-) create mode 100644 tests/panic_test/src/lib.rs delete mode 100644 tests/panic_test/src/main.rs diff --git a/solutions/panic/src/lib.rs b/solutions/panic/src/lib.rs index a9ac22e1..e3fc5fec 100644 --- a/solutions/panic/src/lib.rs +++ b/solutions/panic/src/lib.rs @@ -1,40 +1,5 @@ -/* -## error types - -### Instructions - -Write a function that tries to open a file and panics if the file -doesn't exist - -*/ - use std::fs::File; pub fn open_file(s: &str) -> File { - let file = File::open(s); - if let Ok(f) = file { - return f; - } else { - panic!("File not found") - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - - #[test] - #[should_panic(expected = "File not found")] - fn test_opening() { - open_file("file.txt"); - } - - #[test] - fn test_opening_existing() { - let filename = "created.txt"; - File::create(filename).unwrap(); - open_file(filename); - fs::remove_file(filename).unwrap(); - } + File::open(s).unwrap() } diff --git a/tests/panic_test/src/lib.rs b/tests/panic_test/src/lib.rs new file mode 100644 index 00000000..155924dc --- /dev/null +++ b/tests/panic_test/src/lib.rs @@ -0,0 +1,16 @@ +use panic::*; +use std::fs::{self, File}; + +#[test] +#[should_panic] +fn test_opening() { + open_file("file.txt"); +} + +#[test] +fn test_opening_existing() { + let filename = "created.txt"; + File::create(filename).unwrap(); + open_file(filename); + fs::remove_file(filename).unwrap(); +} diff --git a/tests/panic_test/src/main.rs b/tests/panic_test/src/main.rs deleted file mode 100644 index 85075faf..00000000 --- a/tests/panic_test/src/main.rs +++ /dev/null @@ -1,29 +0,0 @@ -use panic::*; -use std::fs::{self, File}; - -fn main() { - let filename = "created.txt"; - File::create(filename).unwrap(); - let a = open_file(filename); - println!("{:?}", a); - fs::remove_file(filename).unwrap(); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic(expected = "File not found")] - fn test_opening() { - open_file("file.txt"); - } - - #[test] - fn test_opening_existing() { - let filename = "created.txt"; - File::create(filename).unwrap(); - open_file(filename); - fs::remove_file(filename).unwrap(); - } -} From ef9a7977c763b108c06e329a034bc3b81b2a1295 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 6 Feb 2025 16:18:59 +0000 Subject: [PATCH 03/26] fix(handling) --- solutions/handling/src/lib.rs | 102 +++----------------------------- tests/handling_test/Cargo.toml | 1 + tests/handling_test/a.txt | 1 - tests/handling_test/src/lib.rs | 53 +++++++++++++++++ tests/handling_test/src/main.rs | 63 -------------------- 5 files changed, 62 insertions(+), 158 deletions(-) delete mode 100644 tests/handling_test/a.txt create mode 100644 tests/handling_test/src/lib.rs delete mode 100644 tests/handling_test/src/main.rs diff --git a/solutions/handling/src/lib.rs b/solutions/handling/src/lib.rs index 10f37aaa..09e73aa9 100644 --- a/solutions/handling/src/lib.rs +++ b/solutions/handling/src/lib.rs @@ -1,97 +1,11 @@ -/* -## handling +use std::{fs::OpenOptions, io::Write, path::Path}; -### Instructions +pub fn open_or_create>(path: &P, content: &str) { + let mut f = OpenOptions::new() + .append(true) + .create(true) + .open(path) + .unwrap(); -Write a function, called `open_or_create` that as two arguments: -- `file : &str` which is the name of the files -- `content: &str` being the content to be written into the file - -This functions should try to open a file, if it does not exist creates it. -You should panic, with the error, in case something goes wrong. - -### Example - -```rust -fn main() { - let path = "a.txt"; - File::create(path).unwrap(); - open_or_create(path, "content to be written"); - - let mut file = File::open(path).unwrap(); - - let mut s = String::new(); - file.read_to_string(&mut s).unwrap(); - println!("{}", s); - // output: content to be written -} -``` - -### Notions - -- https://doc.rust-lang.org/std/io/enum.ErrorKind.html -- https://doc.rust-lang.org/std/fs/struct.File.html -*/ - -use std::fs::{File, OpenOptions}; -use std::io::{ErrorKind, Write}; - -pub fn open_or_create(s: &str, content: &str) { - let mut f = match OpenOptions::new().write(true).open(s) { - Ok(file) => file, - Err(ref error) if error.kind() == ErrorKind::NotFound => match File::create(s) { - Ok(fc) => fc, - Err(e) => panic!("{:?}", e), - }, - Err(error) => panic!("{:?}", error), - }; - f.write_all(content.as_bytes()).unwrap(); -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::panic; - - use std::io::prelude::*; - - fn get_file_content(filename: &str) -> String { - let mut file = File::open(filename).unwrap(); - let mut s = String::new(); - file.read_to_string(&mut s).unwrap(); - fs::remove_file(filename).unwrap(); - return s; - } - - #[test] - fn test_if_file_exists() { - let filename = "test_existing_file.txt"; - let content = "hello world!"; - File::create(filename).unwrap(); - open_or_create(filename, content); - - assert_eq!(content, get_file_content(filename)); - } - - #[test] - fn test_create_file() { - let file = "no_existing_file.txt"; - let content = "hello world!"; - open_or_create(file, content); - - assert_eq!(content, get_file_content(file)); - } - #[test] - fn test_error_case() { - let filename = "hello.txt"; - File::create(filename).unwrap(); - let mut perms = fs::metadata(filename).unwrap().permissions(); - perms.set_readonly(true); - fs::set_permissions(filename, perms).unwrap(); - - let result = panic::catch_unwind(|| open_or_create(filename, "test")); - fs::remove_file(filename).unwrap(); - assert!(result.is_err()); - } + f.write(content.as_bytes()).unwrap(); } diff --git a/tests/handling_test/Cargo.toml b/tests/handling_test/Cargo.toml index c8e2cbfb..9d256b4b 100644 --- a/tests/handling_test/Cargo.toml +++ b/tests/handling_test/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] handling = { path = "../../solutions/handling"} +tempfile = "3.16.0" diff --git a/tests/handling_test/a.txt b/tests/handling_test/a.txt deleted file mode 100644 index 7dde7fad..00000000 --- a/tests/handling_test/a.txt +++ /dev/null @@ -1 +0,0 @@ -content to be written \ No newline at end of file diff --git a/tests/handling_test/src/lib.rs b/tests/handling_test/src/lib.rs new file mode 100644 index 00000000..ed3633a1 --- /dev/null +++ b/tests/handling_test/src/lib.rs @@ -0,0 +1,53 @@ +use std::{fs, io::Write}; +use tempfile::NamedTempFile; + +#[test] +fn test_with_non_existing_file() { + let file = NamedTempFile::new().unwrap(); + let path = file.path().to_path_buf(); + file.close().unwrap(); + let content = "hello world!"; + + handling::open_or_create(&path, content); + + assert_eq!(content, fs::read_to_string(path).unwrap()); +} + +#[test] +fn test_with_empty_file() { + let path = NamedTempFile::new().unwrap().into_temp_path(); + let content = "hello world!"; + + handling::open_or_create(&path, content); + + assert_eq!(content, fs::read_to_string(path).unwrap()); +} + +#[test] +fn test_with_file() { + let mut file = NamedTempFile::new().unwrap(); + let initial_content = "some content\n"; + file.write_all(initial_content.as_bytes()).unwrap(); + let path = file.into_temp_path(); + let content = "hello world!"; + + handling::open_or_create(&path, content); + + assert_eq!( + format!("{}{}", initial_content, content), + fs::read_to_string(path).unwrap() + ); +} + +#[test] +#[should_panic] +fn test_with_file_with_insufficient_permissions() { + let file = NamedTempFile::new().unwrap(); + let mut permissions = file.as_file().metadata().unwrap().permissions(); + permissions.set_readonly(true); + file.as_file().set_permissions(permissions).unwrap(); + let path = file.into_temp_path(); + let content = "hello world!"; + + handling::open_or_create(&path, content); +} diff --git a/tests/handling_test/src/main.rs b/tests/handling_test/src/main.rs deleted file mode 100644 index f3f77561..00000000 --- a/tests/handling_test/src/main.rs +++ /dev/null @@ -1,63 +0,0 @@ -// -use std::io::prelude::*; -// -use handling::*; -use std::fs::File; - -fn main() { - let path = "a.txt"; - File::create(path).unwrap(); - open_or_create(path, "content to be written"); - - let mut file = File::open(path).unwrap(); - - let mut s = String::new(); - file.read_to_string(&mut s).unwrap(); - println!("{}", s); - // output: content to be written -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::panic; - fn get_file_content(filename: &str) -> String { - let mut file = File::open(filename).unwrap(); - let mut s = String::new(); - file.read_to_string(&mut s).unwrap(); - fs::remove_file(filename).unwrap(); - return s; - } - - #[test] - fn test_if_file_exists() { - let filename = "test_existing_file.txt"; - let content = "hello world!"; - File::create(filename).unwrap(); - open_or_create(filename, content); - - assert_eq!(content, get_file_content(filename)); - } - - #[test] - fn test_create_file() { - let file = "no_existing_file.txt"; - let content = "hello world!"; - open_or_create(file, content); - - assert_eq!(content, get_file_content(file)); - } - #[test] - fn test_error_case() { - let filename = "hello.txt"; - File::create(filename).unwrap(); - let mut perms = fs::metadata(filename).unwrap().permissions(); - perms.set_readonly(true); - fs::set_permissions(filename, perms).unwrap(); - - let result = panic::catch_unwind(|| open_or_create(filename, "test")); - fs::remove_file(filename).unwrap(); - assert!(result.is_err()); - } -} From b9a1e4e0c2a2b09ac9cd491015945023399918e5 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 6 Feb 2025 17:43:55 +0000 Subject: [PATCH 04/26] fix(profanity_filter) --- solutions/profanity_filter/src/lib.rs | 67 +++---------------------- tests/profanity_filter_test/src/lib.rs | 18 +++++++ tests/profanity_filter_test/src/main.rs | 51 ------------------- 3 files changed, 24 insertions(+), 112 deletions(-) create mode 100644 tests/profanity_filter_test/src/lib.rs delete mode 100644 tests/profanity_filter_test/src/main.rs diff --git a/solutions/profanity_filter/src/lib.rs b/solutions/profanity_filter/src/lib.rs index 20a785d8..d1d124d7 100644 --- a/solutions/profanity_filter/src/lib.rs +++ b/solutions/profanity_filter/src/lib.rs @@ -1,64 +1,9 @@ -#[allow(dead_code)] -pub struct Message { - pub content: String, - pub user: String, -} - -#[allow(dead_code)] -impl Message { - pub fn new(ms: String, u: String) -> Message { - Message { - content: ms, - user: u, - } - } - // allows the student to use options - #[allow(dead_code)] - pub fn send_ms(&self) -> Option<&str> { - match (&self.content[..], "stupid") { - (x, p) if x.contains(p) || x == "" => None, - _ => Some(&self.content), - } - } -} - -// the student can catch the None and return it as an error -#[allow(dead_code)] -pub fn check_ms(ms: &Message) -> (bool, &str) { - match ms.send_ms() { - Some(e) => (true, e), - None => (false, "ERROR: illegal"), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_error_ms() { - let v = vec![ - Message::new("".to_string(), "toby".to_string()), - Message::new("stupid".to_string(), "jack".to_string()), - Message::new("you are stupid".to_string(), "jacob".to_string()), - ]; - for value in v { - let (t, _) = check_ms(&value); - assert!(!t); - } - } +const WORD: &str = "stupid"; - #[test] - fn test_ok_ms() { - let v = vec![ - Message::new("get out of the car".to_string(), "police".to_string()), - Message::new("no!".to_string(), "thief".to_string()), - Message::new("get the werewolf".to_string(), "police".to_string()), - Message::new("wait the wha...".to_string(), "thief".to_string()), - ]; - for value in v { - let (t, _) = check_ms(&value); - assert!(t); - } +pub fn check_ms(message: &str) -> Result<&str, &str> { + if message.is_empty() || message.contains(WORD) { + Err("ERROR: illegal") + } else { + Ok(message) } } diff --git a/tests/profanity_filter_test/src/lib.rs b/tests/profanity_filter_test/src/lib.rs new file mode 100644 index 00000000..cd71813e --- /dev/null +++ b/tests/profanity_filter_test/src/lib.rs @@ -0,0 +1,18 @@ +#[test] +fn test_error_ms() { + ["", "stupid", "you are stupid"] + .into_iter() + .for_each(|m| assert_eq!(Err("ERROR: illegal"), profanity_filter::check_ms(m))); +} + +#[test] +fn test_ok_ms() { + [ + "get out of the car", + "no!", + "get the werewolf", + "wait the what...", + ] + .into_iter() + .for_each(|m| assert_eq!(Ok(m), profanity_filter::check_ms(m))); +} diff --git a/tests/profanity_filter_test/src/main.rs b/tests/profanity_filter_test/src/main.rs deleted file mode 100644 index b56b520d..00000000 --- a/tests/profanity_filter_test/src/main.rs +++ /dev/null @@ -1,51 +0,0 @@ -use profanity_filter::*; - -fn main() { - let m0 = Message::new("hello there".to_string(), "toby".to_string()); - println!("{:?}", check_ms(&m0)); - // output: (true, "hello there") - - let m1 = Message::new("".to_string(), "toby".to_string()); - println!("{:?}", check_ms(&m1)); - // output: (false, "ERROR: illegal") - - let m2 = Message::new("you are stupid".to_string(), "toby".to_string()); - println!("{:?}", check_ms(&m2)); - // output: (false, "ERROR: illegal") - - let m3 = Message::new("stupid".to_string(), "toby".to_string()); - println!("{:?}", check_ms(&m3)); - // output: (false, "ERROR: illegal") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_error_ms() { - let v = vec![ - Message::new("".to_string(), "toby".to_string()), - Message::new("stupid".to_string(), "jack".to_string()), - Message::new("you are stupid".to_string(), "jacob".to_string()), - ]; - for value in v { - let (t, _) = check_ms(&value); - assert!(!t); - } - } - - #[test] - fn test_ok_ms() { - let v = vec![ - Message::new("get out of the car".to_string(), "police".to_string()), - Message::new("no!".to_string(), "thief".to_string()), - Message::new("get the werewolf".to_string(), "police".to_string()), - Message::new("wait the wha...".to_string(), "thief".to_string()), - ]; - for value in v { - let (t, _) = check_ms(&value); - assert!(t); - } - } -} From f9f5a0300a99c62cfb2cc4c14ac221ef61f9b08c Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 7 Feb 2025 16:01:16 +0000 Subject: [PATCH 05/26] fix(question_mark) --- solutions/question_mark/src/lib.rs | 72 +--------------------------- tests/question_mark_test/src/lib.rs | 19 ++++++++ tests/question_mark_test/src/main.rs | 50 ------------------- 3 files changed, 20 insertions(+), 121 deletions(-) create mode 100644 tests/question_mark_test/src/lib.rs delete mode 100644 tests/question_mark_test/src/main.rs diff --git a/solutions/question_mark/src/lib.rs b/solutions/question_mark/src/lib.rs index 4f7aabc3..25b7a9db 100644 --- a/solutions/question_mark/src/lib.rs +++ b/solutions/question_mark/src/lib.rs @@ -1,45 +1,3 @@ -/* -## question_mark - -### Instructions - -You will have to create 3 structures: - -- `One`, that contains one element called `first_layer` it should be an `Option` for the structure `Two`. -- `Two`, that contains one element called `second_layer` it should be an `Option` for the structure `Three`. -- `Three`, that contains one element called `third_layer` it should be an `Option` for the structure `Four`. -- `Four`, that contains one element called `fourth_layer` it should be an `u16` that is an `Option`. - -Beside the structure you must create a function named `get_fourth_layer` that is associated to the `One` structure. -This function should return the `Option` value in the `Four` structure. - -### Example - -```rust -fn main() { - let a = One { - first_layer : Some(Two { - second_layer: Some(Three { - third_layer: Some(Four { - fourth_layer: Some(1000) - }) - }) - }) - }; - - // output: 1000 - println!("{:?}", match a.get_fourth_layer() { - Some(e) => e, - None => 0 - }) -} -``` - -### Notions - -- https://doc.rust-lang.org/stable/rust-by-example/error/option_unwrap/question_mark.html - -*/ #[derive(Clone, Copy)] pub struct One { pub first_layer: Option, @@ -61,35 +19,7 @@ pub struct Four { } impl One { - pub fn get_fourth_layer(&self) -> Option { + pub fn get_fourth_layer(self) -> Option { self.first_layer?.second_layer?.third_layer?.fourth_layer } } - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_value() { - let a = One { - first_layer: Some(Two { - second_layer: Some(Three { - third_layer: Some(Four { - fourth_layer: Some(1000), - }), - }), - }), - }; - let b = One { - first_layer: Some(Two { - second_layer: Some(Three { - third_layer: Some(Four { - fourth_layer: Some(3), - }), - }), - }), - }; - assert_eq!(a.get_fourth_layer(), Some(1000)); - assert_eq!(b.get_fourth_layer(), Some(3)); - } -} diff --git a/tests/question_mark_test/src/lib.rs b/tests/question_mark_test/src/lib.rs new file mode 100644 index 00000000..b2c8474d --- /dev/null +++ b/tests/question_mark_test/src/lib.rs @@ -0,0 +1,19 @@ +use question_mark::*; + +fn create_nested(value: Option) -> One { + One { + first_layer: Some(Two { + second_layer: Some(Three { + third_layer: Some(Four { + fourth_layer: value, + }), + }), + }), + } +} + +#[test] +fn test_value() { + assert_eq!(create_nested(Some(1000)).get_fourth_layer(), Some(1000)); + assert_eq!(create_nested(None).get_fourth_layer(), None); +} diff --git a/tests/question_mark_test/src/main.rs b/tests/question_mark_test/src/main.rs deleted file mode 100644 index ad5f6ea0..00000000 --- a/tests/question_mark_test/src/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -use question_mark::*; - -fn main() { - let a = One { - first_layer: Some(Two { - second_layer: Some(Three { - third_layer: Some(Four { - fourth_layer: Some(1000), - }), - }), - }), - }; - - // output: 1000 - println!( - "{:?}", - match a.get_fourth_layer() { - Some(e) => e, - None => 0, - } - ) -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_value() { - let a = One { - first_layer: Some(Two { - second_layer: Some(Three { - third_layer: Some(Four { - fourth_layer: Some(1000), - }), - }), - }), - }; - let b = One { - first_layer: Some(Two { - second_layer: Some(Three { - third_layer: Some(Four { - fourth_layer: Some(3), - }), - }), - }), - }; - assert_eq!(a.get_fourth_layer(), Some(1000)); - assert_eq!(b.get_fourth_layer(), Some(3)); - } -} From cc8f7557809f3aa85078d2d62c7f11aa54a8fc8d Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 17 Feb 2025 11:49:24 +0000 Subject: [PATCH 06/26] fix(banner) --- solutions/banner/src/lib.rs | 221 ++++------------------------------ tests/banner_test/src/lib.rs | 69 +++++++++++ tests/banner_test/src/main.rs | 130 -------------------- 3 files changed, 95 insertions(+), 325 deletions(-) create mode 100644 tests/banner_test/src/lib.rs delete mode 100644 tests/banner_test/src/main.rs diff --git a/solutions/banner/src/lib.rs b/solutions/banner/src/lib.rs index ae91621c..adc2d3ca 100644 --- a/solutions/banner/src/lib.rs +++ b/solutions/banner/src/lib.rs @@ -1,213 +1,44 @@ -/* -## banner +use std::{collections::HashMap, num::ParseFloatError}; -### Instructions - -`Result` is a better version of the `Option` type that describes possible error instead -of possible absence - -Create a structure called `Flag` that as the following elements: - - - short_hand: String - - long_hand: String - - desc: String - -This structure must have associated to it a function called `opt_flag` that initializes the structure. -Receiving two references strings and returns the structure `Flag`. It should be used like this: - -```rust - let d = Flag::opt_flag("diff", "gives the difference between two numbers"); - - println!("short hand: {}, long hand: {}, description: {}", d.short_hand, d.long_hand, d.desc); - // output: "short hand: -d, long hand: --diff, description: gives the difference between two numbers" -``` - -It will be given a second structure called `FlagsHandler` that has just one element: `flags: HashMap<(String, String), Callback>` -And the following functions associated to it, for you to complete : - - - `add_flag`, that adds to the HashMap the flag and the Callback function. - - `exec_func`, that executes the function using the flag provided and returns the result, that can - be either a string with the value from the callback or an error. - -It will also be provided a `type` called `Callback` being a function that is going to be used in the structure -and functions above. This function will be the callback for the flag associated to it. - -You will have to create the following callback functions : - - - `div`, that converts the reference strings to `float`s and returns the `Result`, being the division of the `float`s - or the standard (std) error: `ParseFloatError`. - - `rem`, that converts the reference strings to `float`s and returns the `Result`, being the remainder of the division - of the `float`s or the standard (std) error `ParseFloatError`. - -### Example - -```rust -fn main() { - let mut handler = FlagsHandler { flags: HashMap::new() }; - - let d = Flag::opt_flag("division", "divides the values, formula (a / b)"); - let r = Flag::opt_flag( - "remainder", - "remainder of the division between two values, formula (a % b)", - ); - - handler.add_flag((d.short_hand, d.long_hand), div); - handler.add_flag((r.short_hand, r.long_hand), rem); - - println!("{:?}", handler.exec_func(("-d".to_string(), "--division".to_string()), &["1.0", "2.0"])); - // output: "0.5" - - println!("{:?}",handler.exec_func(("-r".to_string(), "--remainder".to_string()), &["2.0", "2.0"])); - // output: "0.0" - - println!("{:?}",handler.exec_func(("-d".to_string(), "--division".to_string()), &["a", "2.0"])); - // output: "invalid float literal" - - println!("{:?}",handler.exec_func(("-r".to_string(), "--remainder".to_string()), &["2.0", "fd"])); - // output: "invalid float literal" +#[derive(PartialEq, Eq, Hash)] +pub struct Flag<'a> { + pub short_hand: String, + pub long_hand: String, + pub desc: &'a str, } -``` - -### Notions - -- https://doc.rust-lang.org/rust-by-example/error/result.html -- https://docs.rs/getopts/0.2.18/getopts/struct.Options.html#method.optflag -*/ -use std::collections::HashMap; -use std::num::ParseFloatError; - -#[allow(dead_code)] -pub fn div(a: &str, b: &str) -> Result { - let first_number = a.parse::()?; - let second_number = b.parse::()?; - Ok((first_number / second_number).to_string()) -} -#[allow(dead_code)] -pub fn rem(a: &str, b: &str) -> Result { - let first_number = a.parse::()?; - let second_number = b.parse::()?; - Ok((first_number % second_number).to_string()) +impl<'a> Flag<'a> { + pub fn opt_flag(name: &'a str, d: &'a str) -> Self { + Self { + short_hand: format!("-{}", name.chars().next().unwrap()), + long_hand: format!("--{}", name), + desc: d, + } + } } -#[allow(dead_code)] pub type Callback = fn(&str, &str) -> Result; -#[allow(dead_code)] pub struct FlagsHandler { - pub flags: HashMap<(String, String), Callback>, + pub flags: HashMap, } impl FlagsHandler { - #[allow(dead_code)] - pub fn add_flag(&mut self, flag: (String, String), func: Callback) { - self.flags.insert(flag, func); + pub fn add_flag(&mut self, flag: Flag, func: Callback) { + self.flags.insert(flag.short_hand, func); + self.flags.insert(flag.long_hand, func); } - #[allow(dead_code)] - pub fn exec_func(&mut self, flag: (String, String), argv: &[&str]) -> String { - match self.flags[&flag](argv[0], argv[1]) { - Ok(res) => res, - Err(e) => e.to_string(), - } - } -} -#[derive(Debug)] -pub struct Flag { - pub short_hand: String, - pub long_hand: String, - pub desc: String, -} - -impl Flag { - #[allow(dead_code)] - pub fn opt_flag(l_h: &str, d: &str) -> Flag { - let mut a = "-".to_string(); - a.push_str(&l_h[0..1]); - Flag { - short_hand: a, - long_hand: "--".to_string() + &l_h.to_string(), - desc: d.to_string(), - } + pub fn exec_func(&self, input: &str, argv: &[&str]) -> Result { + let f = self.flags[input]; + f(argv[0], argv[1]).map_err(|e| e.to_string()) } } -#[cfg(test)] -mod tests { - use super::*; - - fn init() -> FlagsHandler { - let d = Flag::opt_flag("division", "divides two numbers"); - let r = Flag::opt_flag( - "remainder", - "gives the remainder of the division between two numbers", - ); - let mut handler = FlagsHandler { - flags: HashMap::new(), - }; - - handler.add_flag((d.short_hand, d.long_hand), div); - handler.add_flag((r.short_hand, r.long_hand), rem); - return handler; - } - - #[test] - fn ok_test() { - let mut handler = init(); - assert_eq!( - handler.exec_func( - ("-d".to_string(), "--division".to_string()), - &["1.0", "2.0"] - ), - "0.5" - ); - assert_eq!( - handler.exec_func( - ("-r".to_string(), "--remainder".to_string()), - &["2.0", "2.0"] - ), - "0" - ); - assert_eq!( - handler.exec_func( - ("-d".to_string(), "--division".to_string()), - &["12.323", "212.32"] - ), - "0.05803975" - ); - assert_eq!( - handler.exec_func( - ("-r".to_string(), "--remainder".to_string()), - &["12.323", "212.32"] - ), - "12.323" - ); - } +pub fn div(a: &str, b: &str) -> Result { + Ok((a.parse::()? / b.parse::()?).to_string()) +} - #[test] - fn error_test() { - let mut handler = init(); - assert_eq!( - handler.exec_func(("-d".to_string(), "--division".to_string()), &["a", "2.0"]), - "invalid float literal" - ); - assert_eq!( - handler.exec_func(("-r".to_string(), "--remainder".to_string()), &["2.0", "f"]), - "invalid float literal" - ); - assert_eq!( - handler.exec_func( - ("-d".to_string(), "--division".to_string()), - &["1.0", "0.0"] - ), - "inf" - ); - assert_eq!( - handler.exec_func( - ("-r".to_string(), "--remainder".to_string()), - &["2.0", "0.0"] - ), - "NaN" - ); - } +pub fn rem(a: &str, b: &str) -> Result { + Ok((a.parse::()? % b.parse::()?).to_string()) } diff --git a/tests/banner_test/src/lib.rs b/tests/banner_test/src/lib.rs new file mode 100644 index 00000000..8e2500cc --- /dev/null +++ b/tests/banner_test/src/lib.rs @@ -0,0 +1,69 @@ +use banner::*; +use std::{collections::HashMap, sync::LazyLock}; + +static HANDLER: LazyLock = LazyLock::new(|| { + let mut handler = FlagsHandler { + flags: HashMap::new(), + }; + + handler.add_flag(Flag::opt_flag("division", "divides two numbers"), div); + handler.add_flag( + Flag::opt_flag( + "remainder", + "gives the remainder of the division between two numbers", + ), + rem, + ); + + handler +}); + +#[test] +fn test_simple() { + for a in ["-d", "--division"] { + assert_eq!(HANDLER.exec_func(a, &["1.0", "2.0"]), Ok("0.5".to_owned())); + } + + for a in ["-r", "--remainder"] { + assert_eq!(HANDLER.exec_func(a, &["2.0", "2.0"]), Ok("0".to_owned())); + } + + for a in ["-d", "--division"] { + assert_eq!( + HANDLER.exec_func(a, &["12.323", "212.32"]), + Ok("0.058039751318764134".to_owned()) + ); + } + + for a in ["-r", "--remainder"] { + assert_eq!( + HANDLER.exec_func(a, &["12.323", "212.32"]), + Ok("12.323".to_owned()) + ); + } +} + +#[test] +fn test_edge_cases() { + for a in ["-d", "--division"] { + assert_eq!( + HANDLER.exec_func(a, &["a", "2.0"]), + Err("invalid float literal".to_owned()) + ); + } + + for a in ["-r", "--remainder"] { + assert_eq!( + HANDLER.exec_func(a, &["2.0", "f"]), + Err("invalid float literal".to_owned()) + ); + } + + for a in ["-d", "--division"] { + assert_eq!(HANDLER.exec_func(a, &["1.0", "0.0"]), Ok("inf".to_owned())); + } + + for a in ["-r", "--remainder"] { + assert_eq!(HANDLER.exec_func(a, &["1.0", "0.0"]), Ok("NaN".to_owned())); + } +} diff --git a/tests/banner_test/src/main.rs b/tests/banner_test/src/main.rs deleted file mode 100644 index 9525bde6..00000000 --- a/tests/banner_test/src/main.rs +++ /dev/null @@ -1,130 +0,0 @@ -use banner::*; -use std::collections::HashMap; - -fn main() { - let mut handler = FlagsHandler { - flags: HashMap::new(), - }; - - let d = Flag::opt_flag("division", "divides the values, formula (a / b)"); - let r = Flag::opt_flag( - "remainder", - "remainder of the division between two values, formula (a % b)", - ); - - handler.add_flag((d.short_hand, d.long_hand), div); - handler.add_flag((r.short_hand, r.long_hand), rem); - - println!( - "{:?}", - handler.exec_func( - ("-d".to_string(), "--division".to_string()), - &["1.0", "2.0"] - ) - ); - // output: "0.5" - - println!( - "{:?}", - handler.exec_func( - ("-r".to_string(), "--remainder".to_string()), - &["2.0", "2.0"] - ) - ); - // output: "0.0" - - println!( - "{:?}", - handler.exec_func(("-d".to_string(), "--division".to_string()), &["a", "2.0"]) - ); - // output: "invalid float literal" - - println!( - "{:?}", - handler.exec_func( - ("-r".to_string(), "--remainder".to_string()), - &["2.0", "fd"] - ) - ); - // output: "invalid float literal" -} - -#[cfg(test)] -mod tests { - use super::*; - - fn init() -> FlagsHandler { - let d = Flag::opt_flag("division", "divides two numbers"); - let r = Flag::opt_flag( - "remainder", - "gives the remainder of the division between two numbers", - ); - let mut handler = FlagsHandler { - flags: HashMap::new(), - }; - - handler.add_flag((d.short_hand, d.long_hand), div); - handler.add_flag((r.short_hand, r.long_hand), rem); - return handler; - } - - #[test] - fn ok_test() { - let mut handler = init(); - assert_eq!( - handler.exec_func( - ("-d".to_string(), "--division".to_string()), - &["1.0", "2.0"] - ), - "0.5" - ); - assert_eq!( - handler.exec_func( - ("-r".to_string(), "--remainder".to_string()), - &["2.0", "2.0"] - ), - "0" - ); - assert_eq!( - handler.exec_func( - ("-d".to_string(), "--division".to_string()), - &["12.323", "212.32"] - ), - "0.05803975" - ); - assert_eq!( - handler.exec_func( - ("-r".to_string(), "--remainder".to_string()), - &["12.323", "212.32"] - ), - "12.323" - ); - } - - #[test] - fn error_test() { - let mut handler = init(); - assert_eq!( - handler.exec_func(("-d".to_string(), "--division".to_string()), &["a", "2.0"]), - "invalid float literal" - ); - assert_eq!( - handler.exec_func(("-r".to_string(), "--remainder".to_string()), &["2.0", "f"]), - "invalid float literal" - ); - assert_eq!( - handler.exec_func( - ("-d".to_string(), "--division".to_string()), - &["1.0", "0.0"] - ), - "inf" - ); - assert_eq!( - handler.exec_func( - ("-r".to_string(), "--remainder".to_string()), - &["2.0", "0.0"] - ), - "NaN" - ); - } -} From f6a682b8debff1808854e2496b0bab6ab17281bb Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Tue, 18 Feb 2025 14:54:01 +0000 Subject: [PATCH 07/26] fix(cipher) --- solutions/cipher/src/lib.rs | 47 +++++++++++--------------------- tests/cipher_test/src/lib.rs | 47 ++++++++++++++++++++++++++++++++ tests/cipher_test/src/main.rs | 50 ----------------------------------- 3 files changed, 63 insertions(+), 81 deletions(-) create mode 100644 tests/cipher_test/src/lib.rs delete mode 100644 tests/cipher_test/src/main.rs diff --git a/solutions/cipher/src/lib.rs b/solutions/cipher/src/lib.rs index d49783e0..85291155 100644 --- a/solutions/cipher/src/lib.rs +++ b/solutions/cipher/src/lib.rs @@ -1,45 +1,30 @@ -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, PartialEq)] pub struct CipherError { - pub validation: bool, pub expected: String, } -impl CipherError { - pub fn new(validation: bool, expected: String) -> CipherError { - CipherError { - validation, - expected, - } - } -} +pub fn cipher(original: &str, ciphered: &str) -> Result<(), CipherError> { + let decoded = decode(original); -pub fn cipher(original: &str, ciphered: &str) -> Option> { - if ciphered == "" || original == "" { - return None; - } - match decode(original) == ciphered { - true => Some(Ok(true)), - false => Some(Err(CipherError::new( - decode(original) == ciphered, - decode(original), - ))), + if decoded == ciphered { + Ok(()) + } else { + Err(CipherError { expected: decoded }) } } +const CHARS: u8 = 'z' as u8 - 'a' as u8; + fn decode(original: &str) -> String { original .chars() - .map(|letter| match letter.is_ascii_alphabetic() { - true => { - match letter.is_uppercase() { - true => (((25 - (letter as u32 - 'A' as u32)) + 'A' as u32) as u8 as char) - .to_string(), - false => (((25 - (letter as u32 - 'a' as u32)) + 'a' as u32) as u8 as char) - .to_string(), - } + .map(|l| { + if l.is_ascii_alphabetic() { + let offset = (if l.is_ascii_uppercase() { 'A' } else { 'a' }) as u8; + ((CHARS - (l as u8 - offset)) + offset) as char + } else { + l } - false => letter.to_string(), }) - .collect::>() - .join("") + .collect() } diff --git a/tests/cipher_test/src/lib.rs b/tests/cipher_test/src/lib.rs new file mode 100644 index 00000000..639a2e93 --- /dev/null +++ b/tests/cipher_test/src/lib.rs @@ -0,0 +1,47 @@ +use cipher::*; + +#[test] +fn test_ok_values() { + assert_eq!(cipher("1Hello 2world!", "1Svool 2dliow!"), Ok(())); + assert_eq!(cipher("asdasd", "zhwzhw"), Ok(())); + assert_eq!(cipher("3(/&%fsd 32das", "3(/&%uhw 32wzh"), Ok(())); +} + +#[test] +fn test_empty_values() { + assert_eq!(cipher("", ""), Ok(())); + assert_eq!( + cipher("", "1Svool 2dliow!"), + Err(CipherError { + expected: "".to_owned() + }) + ); + assert_eq!( + cipher("1Hello 2world!", ""), + Err(CipherError { + expected: "1Svool 2dliow!".to_owned() + }) + ); +} + +#[test] +fn test_errors() { + assert_eq!( + cipher("1Hello 2world!", "1svool 2dliow!"), + Err(CipherError { + expected: String::from("1Svool 2dliow!") + }) + ); + assert_eq!( + cipher("asdasd", "lkdas"), + Err(CipherError { + expected: String::from("zhwzhw") + }) + ); + assert_eq!( + cipher("3(/&%sd 32das", "3(/&%uhw 32wzh"), + Err(CipherError { + expected: String::from("3(/&%hw 32wzh") + }) + ); +} diff --git a/tests/cipher_test/src/main.rs b/tests/cipher_test/src/main.rs deleted file mode 100644 index 4691bc85..00000000 --- a/tests/cipher_test/src/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -use cipher::*; - -fn main() { - println!("{:?}", cipher("1Hello 2world!", "1Svool 2dliow!")); - println!("{:?}", cipher("1Hello 2world!", "svool")); - println!("{:?}", cipher("", "svool")); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_ok_values() { - assert_eq!(cipher("1Hello 2world!", "1Svool 2dliow!"), Some(Ok(true))); - assert_eq!(cipher("asdasd", "zhwzhw"), Some(Ok(true))); - assert_eq!(cipher("3(/&%fsd 32das", "3(/&%uhw 32wzh"), Some(Ok(true))); - } - - #[test] - fn test_empty_values() { - assert_eq!(cipher("", "1Svool 2dliow!"), None); - assert_eq!(cipher("1Hello 2world!", ""), None); - } - - #[test] - fn test_errors() { - assert_eq!( - cipher("1Hello 2world!", "1svool 2dliow!"), - Some(Err(CipherError { - validation: false, - expected: String::from("1Svool 2dliow!") - })) - ); - assert_eq!( - cipher("asdasd", "lkdas"), - Some(Err(CipherError { - validation: false, - expected: String::from("zhwzhw") - })) - ); - assert_eq!( - cipher("3(/&%sd 32das", "3(/&%uhw 32wzh"), - Some(Err(CipherError { - validation: false, - expected: String::from("3(/&%hw 32wzh") - })) - ); - } -} From b4f3a277479705ab522b1bcb8f3ffda8504e50c8 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 19 Feb 2025 14:14:59 +0000 Subject: [PATCH 08/26] fix(error_types) --- solutions/error_types/src/lib.rs | 91 ++++++------------- tests/error_types_test/src/lib.rs | 73 ++++++++++++++++ tests/error_types_test/src/main.rs | 135 ----------------------------- 3 files changed, 99 insertions(+), 200 deletions(-) create mode 100644 tests/error_types_test/src/lib.rs delete mode 100644 tests/error_types_test/src/main.rs diff --git a/solutions/error_types/src/lib.rs b/solutions/error_types/src/lib.rs index 13571eb2..d70f3b8d 100644 --- a/solutions/error_types/src/lib.rs +++ b/solutions/error_types/src/lib.rs @@ -2,14 +2,14 @@ pub use chrono::{NaiveDate, Utc}; #[derive(Debug, Eq, PartialEq)] pub struct FormError { - pub form_values: (String, String), + pub form_values: (&'static str, String), pub date: String, - pub err: String, + pub err: &'static str, } impl FormError { - pub fn new(field_name: String, field_value: String, err: String) -> FormError { - FormError { + pub fn new(field_name: &'static str, field_value: String, err: &'static str) -> Self { + Self { form_values: (field_name, field_value), date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), err, @@ -19,73 +19,34 @@ impl FormError { #[derive(Debug, Eq, PartialEq)] pub struct Form { - pub first_name: String, - pub last_name: String, - pub birth: NaiveDate, - pub birth_location: String, + pub name: String, pub password: String, } impl Form { - pub fn new( - first_name: String, - last_name: String, - birth: NaiveDate, - birth_location: String, - password: String, - ) -> Form { - Form { - first_name, - last_name, - birth, - birth_location, - password, + pub fn validate(&self) -> Result<(), FormError> { + if self.name.is_empty() { + return Err(FormError::new( + "first_name", + self.name.clone(), + "Username is empty", + )); } - } - - pub fn validate(&self) -> Result, FormError> { - let v: Vec<&str> = vec![ - match &self.first_name { - x if x == &String::from("") => Err(self.user_name()), - _ => Ok("Valid first name"), - }?, - match &self.password { - x if x.len() < 8 => Err(self.has_eight_char()), - _ if !self.validate_ascii_char() => Err(self.has_diff_ascii_char()), - _ => Ok("Valid password"), - }?, - ]; - Ok(v) - } - fn user_name(&self) -> FormError { - FormError::new( - String::from("first_name"), - self.first_name.clone(), - String::from("No user name"), - ) - } - - // all this is the part to validate all the parts of the form - fn has_eight_char(&self) -> FormError { - FormError::new( - String::from("password"), - self.password.clone(), - String::from("At least 8 characters"), - ) - } - - fn has_diff_ascii_char(&self) -> FormError { - FormError::new(String::from("password"), self.password.clone(), String::from("Combination of different ASCII character types (numbers, letters and none alphanumeric characters)")) - } + let password_error = + |m: &'static str| Err(FormError::new("password", self.password.clone(), m)); + + if self.password.len() < 8 { + return password_error("Password should be at least 8 characters long"); + } else if !(self.password.chars().any(|c| c.is_ascii_digit()) + && self.password.chars().any(|c| c.is_ascii_alphabetic()) + && self.password.chars().any(|c| c.is_ascii_punctuation())) + { + return password_error( + "Password should be a combination of ASCII numbers, letters and symbols", + ); + } - fn validate_ascii_char(&self) -> bool { - let chars: Vec = self.password.chars().collect(); - chars.iter().any(|c| c.is_digit(10)) && chars.iter().any(|c| !c.is_alphanumeric()) + Ok(()) } } - -#[allow(dead_code)] -fn create_date(date: &str) -> NaiveDate { - NaiveDate::parse_from_str(date, "%Y-%m-%d").unwrap() -} diff --git a/tests/error_types_test/src/lib.rs b/tests/error_types_test/src/lib.rs new file mode 100644 index 00000000..ca7baa21 --- /dev/null +++ b/tests/error_types_test/src/lib.rs @@ -0,0 +1,73 @@ +use error_types::*; + +#[test] +fn test_error_type() { + let cases = [ + ( + Form { + name: "Katy".to_owned(), + password: "qwTw12&%$3sa1dty_".to_owned(), + }, + Ok(()), + ), + ( + Form { + name: "".to_owned(), + password: String::from("qwTw12&%$3sa1dty_"), + }, + Err(FormError { + form_values: ("first_name", "".to_owned()), + date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), + err: "Username is empty", + }), + ), + ( + Form { + name: "Someone".to_owned(), + password: "12345".to_owned(), + }, + Err(FormError { + form_values: ("password", "12345".to_owned()), + date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), + err: "Password should be at least 8 characters long", + }), + ), + ( + Form { + name: "Someone".to_owned(), + password: "sdASDsrW".to_owned(), + }, + Err(FormError { + form_values: ("password", "sdASDsrW".to_owned()), + date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), + err: "Password should be a combination of ASCII numbers, letters and symbols", + }), + ), + ( + Form { + name: "Someone".to_owned(), + password: "dsGE1SAD213".to_owned(), + }, + Err(FormError { + form_values: ("password", "dsGE1SAD213".to_owned()), + date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), + err: "Password should be a combination of ASCII numbers, letters and symbols", + }), + ), + ( + Form { + name: "Someone".to_owned(), + password: "dsaSD&%DF!?=".to_owned(), + }, + Err(FormError { + form_values: ("password", String::from("dsaSD&%DF!?=")), + date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), + err: "Password should be a combination of ASCII numbers, letters and symbols", + }), + ), + ]; + + for (form, expected) in cases { + assert_eq!(form.validate(), expected); + } +} diff --git a/tests/error_types_test/src/main.rs b/tests/error_types_test/src/main.rs deleted file mode 100644 index 84d066e3..00000000 --- a/tests/error_types_test/src/main.rs +++ /dev/null @@ -1,135 +0,0 @@ -use error_types::*; - -#[allow(dead_code)] -fn create_date(date: &str) -> NaiveDate { - NaiveDate::parse_from_str(date, "%Y-%m-%d").unwrap() -} - -fn main() { - let mut form_output = Form::new( - String::from("Lee"), - String::from("Silva"), - create_date("2015-09-05"), - String::from("Africa"), - String::from("qwqwsa1dty_"), - ); - - println!("{:?}", form_output); - println!("{:?}", form_output.validate().unwrap()); - - form_output.first_name = String::from(""); - println!("{:?}", form_output.validate().unwrap_err()); - - form_output.first_name = String::from("as"); - form_output.password = String::from("dty_1"); - println!("{:?}", form_output.validate().unwrap_err()); - - form_output.password = String::from("asdasASd(_"); - println!("{:?}", form_output.validate().unwrap_err()); - - form_output.password = String::from("asdasASd123SA"); - println!("{:?}", form_output.validate().unwrap_err()); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug)] - struct TestForm<'a> { - form: Form, - validation: Result, FormError>, - } - - impl<'a> TestForm<'_> { - // all test cases - fn new() -> Vec> { - vec![ - TestForm { - form : Form::new( - String::from("Katy"), - String::from("Silva"), - create_date("2015-09-05"), - String::from("Africa"), - String::from("qwTw12&%$3sa1dty_")), - validation: Ok(vec!["Valid first name", "Valid password"]), - }, - TestForm { - form : Form::new( - String::from(""), - String::from("Bear"), - create_date("2015-09-05"), - String::from("Africa"), - String::from("qwTw12&%$3sa1dty_")), - validation: Err(FormError { - form_values: (String::from("first_name"), - String::from("")), - date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), - err: String::from("No user name")}), - }, - TestForm { - form : Form::new( - String::from("Someone"), - String::from("Bear"), - create_date("2015-09-05"), - String::from("Africa"), - String::from("12345")), - validation: Err(FormError { - form_values: (String::from("password"), String::from("12345")), - date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), - err: String::from("At least 8 characters") }), - }, - TestForm { - form : Form::new( - String::from("Someone"), - String::from("Bear"), - create_date("2015-09-05"), - String::from("Africa"), - String::from("sdASDsrW")), - validation: Err(FormError { - form_values: (String::from("password"), String::from("sdASDsrW")), - date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), - err: String::from("Combination of different ASCII character types (numbers, letters and none alphanumeric characters)") }), - }, - TestForm { - form : Form::new( - String::from("Someone"), - String::from("Bear"), - create_date("2015-09-05"), - String::from("Africa"), - String::from("dsGE1SAD213")), - validation: Err(FormError { - form_values: (String::from("password"), String::from("dsGE1SAD213")), - date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), - err: String::from("Combination of different ASCII character types (numbers, letters and none alphanumeric characters)") }), - }, - TestForm { - form : Form::new( - String::from("Someone"), - String::from("Bear"), - create_date("2015-09-05"), - String::from("Africa"), - String::from("dsaSD&%DF!?=")), - validation: Err(FormError { - form_values: (String::from("password"), String::from("dsaSD&%DF!?=")), - date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), - err: String::from("Combination of different ASCII character types (numbers, letters and none alphanumeric characters)") }), - } - ] - } - } - - #[test] - fn test_error_type() { - let form_cases = TestForm::new(); - - for v in form_cases { - assert_eq!( - v.form.validate(), - v.validation, - "Tested with {:?}", - v.validation.as_ref().err().unwrap().form_values - ); - } - } -} From 8bc2b4de4d80596002f6b42b1cf339b5aae4dc2f Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 20 Feb 2025 11:15:46 +0000 Subject: [PATCH 09/26] fix(boxing_todo) --- solutions/boxing_todo/src/err.rs | 29 ++++--- solutions/boxing_todo/src/lib.rs | 65 +++++---------- tests/boxing_todo_test/Cargo.toml | 1 + tests/boxing_todo_test/src/lib.rs | 89 ++++++++++++++++++++ tests/boxing_todo_test/src/main.rs | 127 ----------------------------- 5 files changed, 125 insertions(+), 186 deletions(-) create mode 100644 tests/boxing_todo_test/src/lib.rs delete mode 100644 tests/boxing_todo_test/src/main.rs diff --git a/solutions/boxing_todo/src/err.rs b/solutions/boxing_todo/src/err.rs index fe8e1a40..a3c46fb0 100644 --- a/solutions/boxing_todo/src/err.rs +++ b/solutions/boxing_todo/src/err.rs @@ -1,17 +1,17 @@ -use std::error::Error; -use std::fmt; -use std::fmt::Display; +use std::{ + error::Error, + fmt::{self, Display}, +}; #[derive(Debug)] pub enum ParseErr { - Malformed(Box), Empty, + Malformed(Box), } -// required by error trait impl Display for ParseErr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Fail to parse todo") + write!(f, "Failed to parse todo file") } } @@ -20,16 +20,9 @@ pub struct ReadErr { pub child_err: Box, } -// required by error trait impl Display for ReadErr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Fail to read todo file") - } -} - -impl Error for ReadErr { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&*self.child_err) + write!(f, "Failed to read todo file") } } @@ -37,7 +30,13 @@ impl Error for ParseErr { fn source(&self) -> Option<&(dyn Error + 'static)> { match &self { ParseErr::Empty => None, - _ => Some(&*self), + _ => Some(self), } } } + +impl Error for ReadErr { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(self.child_err.as_ref()) + } +} diff --git a/solutions/boxing_todo/src/lib.rs b/solutions/boxing_todo/src/lib.rs index 3b6599d1..7c00cf95 100644 --- a/solutions/boxing_todo/src/lib.rs +++ b/solutions/boxing_todo/src/lib.rs @@ -1,9 +1,8 @@ mod err; pub use err::{ParseErr, ReadErr}; -pub use std::error::Error; -use json::{parse, stringify}; -use std::fs::read_to_string; +use std::error::Error; +use std::fs; #[derive(Debug, Eq, PartialEq)] pub struct Task { @@ -12,55 +11,33 @@ pub struct Task { pub level: u32, } -impl Task { - pub fn new(id: u32, description: String, level: u32) -> Task { - Task { - id, - description, - level, - } - } -} - #[derive(Debug, Eq, PartialEq)] pub struct TodoList { pub title: String, pub tasks: Vec, } + impl TodoList { - pub fn new(title: String, tasks: Vec) -> TodoList { - TodoList { title, tasks } - } pub fn get_todo(path: &str) -> Result> { - let todo_raw = read_todos(path); - let parsed_todos = parse_todos(&todo_raw?)?; - Ok(parsed_todos) - } -} + let contents = fs::read_to_string(path).map_err(|e| ReadErr { + child_err: Box::new(e), + })?; -pub fn read_todos(path: &str) -> Result> { - let raw = read_to_string(path).map_err(|e| ReadErr { - child_err: Box::new(e), - })?; - Ok(raw) -} + let contents = json::parse(&contents).map_err(|e| ParseErr::Malformed(Box::new(e)))?; + if contents["tasks"].is_empty() { + return Err(ParseErr::Empty.into()); + } -pub fn parse_todos(todo_str: &str) -> Result> { - let parset = parse(todo_str).map_err(|e| ParseErr::Malformed(Box::new(e)))?; - if parset["tasks"].is_empty() { - return Err(ParseErr::Empty.into()); - } - let mut v = vec![]; - for i in 0..parset["tasks"].len() { - let a = &parset["tasks"][i]; - let task = stringify(a["description"].clone()); - v.push(Task::new( - stringify(a["id"].clone()).parse().unwrap(), - task.get(1..task.len() - 1).unwrap().to_string(), - stringify(a["level"].clone()).parse().unwrap(), - )); + Ok(Self { + title: contents["title"].as_str().unwrap().to_owned(), + tasks: contents["tasks"] + .members() + .map(|m| Task { + id: m["id"].as_u32().unwrap(), + description: m["description"].as_str().unwrap().to_owned(), + level: m["level"].as_u32().unwrap(), + }) + .collect(), + }) } - let title = stringify(parset["title"].clone()); - let todo_list = TodoList::new(title.get(1..title.len() - 1).unwrap().to_string(), v); - Ok(todo_list) } diff --git a/tests/boxing_todo_test/Cargo.toml b/tests/boxing_todo_test/Cargo.toml index 40e3bdce..dbb967b9 100644 --- a/tests/boxing_todo_test/Cargo.toml +++ b/tests/boxing_todo_test/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" [dependencies] boxing_todo = { path = "../../solutions/boxing_todo"} json="0.12.4" +tempfile = "3.17.1" diff --git a/tests/boxing_todo_test/src/lib.rs b/tests/boxing_todo_test/src/lib.rs new file mode 100644 index 00000000..e4b17704 --- /dev/null +++ b/tests/boxing_todo_test/src/lib.rs @@ -0,0 +1,89 @@ +use boxing_todo::*; + +use json::{object, JsonValue}; +use std::error::Error; +use std::io::{self, Write}; +use tempfile::NamedTempFile; + +fn write_and_read_test( + write_f: impl FnOnce(&mut NamedTempFile), +) -> Result> { + let mut file = NamedTempFile::new().unwrap(); + write_f(&mut file); + + let path = file.path().to_str().unwrap(); + TodoList::get_todo(path) +} + +#[inline] +fn json_to_file_to_mem(obj: JsonValue) -> Result> { + write_and_read_test(|f| obj.write(f).unwrap()) +} + +#[test] +fn test_valid_todo() { + let r#struct = TodoList { + title: "todo list for something".to_owned(), + tasks: vec![ + Task { + id: 0, + description: "do this".to_owned(), + level: 0, + }, + Task { + id: 1, + description: "do that".to_owned(), + level: 5, + }, + ], + }; + + let obj = object! { + "title" : "todo list for something", + "tasks": [ + { "id": 0, "description": "do this", "level": 0 }, + { "id": 1, "description": "do that", "level": 5 } + ] + }; + + assert_eq!(r#struct, json_to_file_to_mem(obj).unwrap()); +} + +#[test] +fn test_empty_tasks() { + let obj = object! { + "title" : "empty tasks", + "tasks": [] + }; + + let result = json_to_file_to_mem(obj).unwrap_err(); + + assert!(matches!(result.downcast_ref().unwrap(), ParseErr::Empty)); + assert!(result.source().is_none()); + assert_eq!(result.to_string(), "Failed to parse todo file"); +} + +#[test] +fn test_read_err() { + let result = TodoList::get_todo("invalid_file.json").unwrap_err(); + + let ReadErr { child_err } = result.downcast_ref().unwrap(); + + assert!(child_err.is::()); + assert!(result.source().unwrap().is::()); + assert_eq!(result.to_string(), "Failed to read todo file"); +} + +#[test] +fn test_parse_err_malformed() { + let result = write_and_read_test(|f| f.write_all(r#"{"something": ,}"#.as_bytes()).unwrap()) + .unwrap_err(); + + let ParseErr::Malformed(e) = result.downcast_ref::().unwrap() else { + panic!() + }; + + assert!(e.is::()); + assert!(result.source().unwrap().is::()); + assert_eq!(result.to_string(), "Failed to parse todo file"); +} diff --git a/tests/boxing_todo_test/src/main.rs b/tests/boxing_todo_test/src/main.rs deleted file mode 100644 index 48ae8f07..00000000 --- a/tests/boxing_todo_test/src/main.rs +++ /dev/null @@ -1,127 +0,0 @@ -use boxing_todo::*; - -// Note that you can create some todo list your self to test it, but you can find the JSON files that -// are being tested [here](https://github.com/01-edu/public/blob/master/subjects/boxing_todo) -fn main() { - let todos = TodoList::get_todo("todo.json"); - match todos { - Ok(list) => println!("{:?}", list), - Err(e) => { - println!("{} {:?}", e.to_string(), e.source()); - } - } - - let todos = TodoList::get_todo("malformed_object.json"); - match todos { - Ok(list) => println!("{:?}", list), - Err(e) => { - println!("{} {:?}", e.to_string(), e.source()); - } - } - - let todos = TodoList::get_todo("permission_err.json"); - match todos { - Ok(list) => println!("{:?}", list), - Err(e) => { - println!("{} {:?}", e.to_string(), e.source()); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use json::{object, JsonValue}; - use std::fs; - use std::fs::{File, OpenOptions}; - use std::io::Write; - - fn new_todo(s: String, v: Vec) -> TodoList { - TodoList { title: s, tasks: v } - } - - fn run(s: JsonValue, f: &str) -> Result> { - File::create(f)?; - let mut file = OpenOptions::new().append(true).open(f)?; - file.write_all(s.dump().as_bytes())?; - let result = TodoList::get_todo(f); - fs::remove_file(f)?; - return result; - } - - #[test] - fn test_good_todo() { - let file_name = "todo.json"; - let good_struct = new_todo( - String::from("todo list for something"), - vec![ - Task { - id: 0, - description: "do this".to_string(), - level: 0, - }, - Task { - id: 1, - description: "do that".to_string(), - level: 5, - }, - ], - ); - let obj = object! { - "title" : "todo list for something", - "tasks": [ - { "id": 0, "description": "do this", "level": 0 }, - { "id": 1, "description": "do that", "level": 5 } - ] - }; - - let result = run(obj, file_name).unwrap(); - - assert_eq!(result.title, good_struct.title); - assert_eq!(&result.tasks, &good_struct.tasks); - } - - #[test] - fn test_empty_tasks() { - let result = run( - object! { - "title" : "empty tasks", - "tasks": []}, - "empty_tasks.json", - ) - .unwrap_err(); - - assert_eq!(result.to_string(), "Fail to parse todo"); - assert!(result.source().is_none()); - } - - #[test] - fn test_read() { - let result = TodoList::get_todo("no_file.json").unwrap_err(); - assert_eq!(result.to_string(), "Fail to read todo file"); - } - - #[test] - #[should_panic( - expected = "Fail to read todo file Some(Os { code: 2, kind: NotFound, message: \"No such file or directory\" })" - )] - fn test_read_error() { - let result = TodoList::get_todo("no_file.json"); - result.unwrap_or_else(|e| panic!("{} {:?}", e.to_string(), e.source())); - } - - #[test] - #[should_panic( - expected = "Fail to parse todo Some(Malformed(UnexpectedCharacter { ch: \',\', line: 1, column: 15 }))" - )] - fn test_malformed_error() { - let file_name = "malformed.json"; - File::create(file_name).unwrap(); - let mut file = OpenOptions::new().append(true).open(file_name).unwrap(); - file.write_all(r#"{"something": ,}"#.as_bytes()).unwrap(); - let result = TodoList::get_todo(file_name); - fs::remove_file(file_name).unwrap(); - - result.unwrap_or_else(|e| panic!("{} {:?}", e.to_string(), e.source())); - } -} From a580a4cb789befe3497fed17b14411942fe3b142 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Thu, 20 Feb 2025 11:36:31 +0000 Subject: [PATCH 10/26] fix(boxing_todo): rm unneeded files --- solutions/boxing_todo/malformed_object.json | 3 --- solutions/boxing_todo/todo.json | 7 ------- solutions/boxing_todo/todo_empty.json | 4 ---- 3 files changed, 14 deletions(-) delete mode 100644 solutions/boxing_todo/malformed_object.json delete mode 100644 solutions/boxing_todo/todo.json delete mode 100644 solutions/boxing_todo/todo_empty.json diff --git a/solutions/boxing_todo/malformed_object.json b/solutions/boxing_todo/malformed_object.json deleted file mode 100644 index e1651765..00000000 --- a/solutions/boxing_todo/malformed_object.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "something": , -} \ No newline at end of file diff --git a/solutions/boxing_todo/todo.json b/solutions/boxing_todo/todo.json deleted file mode 100644 index 2341ac78..00000000 --- a/solutions/boxing_todo/todo.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "title" : "TODO LIST FOR PISCINE RUST", - "tasks": [ - { "id": 0, "description": "do this", "level": 0 }, - { "id": 1, "description": "do that", "level": 5 } - ] -} \ No newline at end of file diff --git a/solutions/boxing_todo/todo_empty.json b/solutions/boxing_todo/todo_empty.json deleted file mode 100644 index 9dfc54f5..00000000 --- a/solutions/boxing_todo/todo_empty.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title" : "TODO LIST FOR PISCINE RUST", - "tasks": [] -} \ No newline at end of file From 68347068e54ef5b0d73dcd6c883e393fbb69ea91 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 14 Mar 2025 01:04:37 +0000 Subject: [PATCH 11/26] fix(error_types): had some inconsistencies --- solutions/error_types/src/lib.rs | 4 ++-- tests/error_types_test/src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/solutions/error_types/src/lib.rs b/solutions/error_types/src/lib.rs index d70f3b8d..6eabea40 100644 --- a/solutions/error_types/src/lib.rs +++ b/solutions/error_types/src/lib.rs @@ -1,4 +1,4 @@ -pub use chrono::{NaiveDate, Utc}; +pub use chrono::Utc; #[derive(Debug, Eq, PartialEq)] pub struct FormError { @@ -27,7 +27,7 @@ impl Form { pub fn validate(&self) -> Result<(), FormError> { if self.name.is_empty() { return Err(FormError::new( - "first_name", + "name", self.name.clone(), "Username is empty", )); diff --git a/tests/error_types_test/src/lib.rs b/tests/error_types_test/src/lib.rs index ca7baa21..b9329aea 100644 --- a/tests/error_types_test/src/lib.rs +++ b/tests/error_types_test/src/lib.rs @@ -13,10 +13,10 @@ fn test_error_type() { ( Form { name: "".to_owned(), - password: String::from("qwTw12&%$3sa1dty_"), + password: "qwTw12&%$3sa1dty_".to_owned(), }, Err(FormError { - form_values: ("first_name", "".to_owned()), + form_values: ("name", "".to_owned()), date: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), err: "Username is empty", }), From 998f39a9c6c41c741806ffea54d038f213adeccd Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 14 Mar 2025 01:33:23 +0000 Subject: [PATCH 12/26] fix(panic): changed output inconsistency; also improved a test a bit --- tests/panic_test/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/panic_test/src/lib.rs b/tests/panic_test/src/lib.rs index 155924dc..f250526f 100644 --- a/tests/panic_test/src/lib.rs +++ b/tests/panic_test/src/lib.rs @@ -2,7 +2,7 @@ use panic::*; use std::fs::{self, File}; #[test] -#[should_panic] +#[should_panic(expected = "No such file or directory")] fn test_opening() { open_file("file.txt"); } From 8b54633862524fc9df243ccafdc964fd42d03457 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 17 Mar 2025 17:06:30 +0000 Subject: [PATCH 13/26] fix(middle_day && does_it_fit) --- solutions/does_it_fit/src/areas_volumes.rs | 20 +- solutions/does_it_fit/src/lib.rs | 211 ++++----------------- solutions/middle_day/Cargo.toml | 2 +- solutions/middle_day/src/lib.rs | 44 ++--- 4 files changed, 58 insertions(+), 219 deletions(-) diff --git a/solutions/does_it_fit/src/areas_volumes.rs b/solutions/does_it_fit/src/areas_volumes.rs index 3ee34f01..f58f554b 100644 --- a/solutions/does_it_fit/src/areas_volumes.rs +++ b/solutions/does_it_fit/src/areas_volumes.rs @@ -9,42 +9,42 @@ pub enum GeometricalVolumes { Cube, Sphere, Cone, - Pyramid, + TriangularPyramid, Parallelepiped, } -pub fn square_area(side: usize) -> usize { +pub(crate) fn square_area(side: usize) -> usize { side.pow(2) } -pub fn triangle_area(base: usize, height: usize) -> f64 { +pub(crate) fn triangle_area(base: usize, height: usize) -> f64 { (base as f64 * height as f64) / 2.0 } -pub fn circle_area(radius: usize) -> f64 { +pub(crate) fn circle_area(radius: usize) -> f64 { std::f64::consts::PI * (radius.pow(2) as f64) } -pub fn rectangle_area(side_a: usize, side_b: usize) -> usize { +pub(crate) fn rectangle_area(side_a: usize, side_b: usize) -> usize { side_a * side_b } -pub fn cube_volume(side: usize) -> usize { +pub(crate) fn cube_volume(side: usize) -> usize { side.pow(3) } -pub fn sphere_volume(radius: usize) -> f64 { +pub(crate) fn sphere_volume(radius: usize) -> f64 { (4.0 / 3.0) * std::f64::consts::PI * (radius.pow(3) as f64) } -pub fn triangular_pyramid_volume(base_area: f64, height: usize) -> f64 { +pub(crate) fn triangular_pyramid_volume(base_area: f64, height: usize) -> f64 { (base_area * height as f64) / 3.0 } -pub fn parallelepiped_volume(side_a: usize, side_b: usize, side_c: usize) -> usize { +pub(crate) fn parallelepiped_volume(side_a: usize, side_b: usize, side_c: usize) -> usize { side_a * side_b * side_c } -pub fn cone_volume(base_radius: usize, height: usize) -> f64 { +pub(crate) fn cone_volume(base_radius: usize, height: usize) -> f64 { (1.0 / 3.0) * std::f64::consts::PI * base_radius.pow(2) as f64 * height as f64 } diff --git a/solutions/does_it_fit/src/lib.rs b/solutions/does_it_fit/src/lib.rs index fd635b22..8176f2df 100644 --- a/solutions/does_it_fit/src/lib.rs +++ b/solutions/does_it_fit/src/lib.rs @@ -1,188 +1,49 @@ -/* -## doe_it_fit - -### Instructions - -Using the `areas_volumes` module provided, create two functions: - -- `area_fit` that receives 6 arguments: - - `x` and `y`, size of the square in which it is going to be tried to fit the geometrical shapes (both usize) - - `objects`, the type of geometrical shape(s) that it is going to be tried to be fitted in the square (areas_volumes::GeometricalShapes) - - `times`, the number of geometrical shapes that are going to be tried to be fitted in the square (usize) - - `a` and `b`, the dimensions that the plane(s) shape(s) passed will have (both usize) - - `a` will refer to the side of the Square, the radius of the Circle, the side_a of the Rectangle or the base of the Triangle - - `b` will refer to the side_b of the Rectangle or the height of the Triangle - -- `area_fit` should return if the geometrical shape(s) fit inside of the square. - - `volume_fit` that receives 8 arguments: - - `x`, `y` and `z`, size of the box in which it is going to be tried to fit the geometrical volumes (both usize) - - `objects`, the type of geometrical volume(s) that it is going to be tried to be fitted in the box (areas_volumes::GeometricalVolumes) - - `times`, the number of geometrical volumes that are going to be tried to be fitted in the box (usize) - - `a`, `b` and `c`, the dimensions that the geometrical volume(s) passed will have (all of them usize) - - `a` will refer to the side of the Cube, the radius of the Sphere, the side_a of the Parallelepiped, the area of the base of the Triangular Pyramid or the base radius of the Cone - - `b` will refer to the side_b of the Parallelepiped, the height of the Triangular Pyramid or the height of the Cone - - `c` will refer to the side_c of the Parallelepiped -- `volume_fit` should return if the geometrical volume(s) fit inside of the box. - -### Expected Functions (and Structures) - -```rs -pub fn area_fit( - x: usize, - y: usize, - objects: areas_volumes::GeometricalShapes, - times: usize, - a: usize, - b: usize, -) {} -pub fn volume_fit( - x: usize, - y: usize, - z: usize, - objects: areas_volumes::GeometricalVolumes, - times: usize, - a: usize, - b: usize, - c: usize, -) {} -``` -### Usage +pub mod areas_volumes; -Here is a program to test your function: +pub use areas_volumes::*; -```rust -fn main() { - println!( - "Does 100 rectangles (2x1) fit in a 2 by 4 square? {}", - area_fit(2, 4, GeometricalShapes::Rectangle, 100, 2, 1) - ); - println!( - "Does 3 triangles (5 base and 3 height) fit in a 5 by 5 square? {}", - area_fit(5, 5, GeometricalShapes::Triangle, 3, 5, 3) - ); - println!( - "Does 3 spheres (2 radius) fit in a 5 by 5 by 5 box? {}", - volume_fit(5, 5, 5, GeometricalVolumes::Sphere, 3, 2, 0, 0) - ); - println!( - "Does 3 triangles (5 base and 3 height) fit in a 5 by 7 by 5 box? {}", - volume_fit(5, 7, 5, GeometricalVolumes::Parallelepiped, 1, 6, 7, 4) - ); +#[inline] +fn generic_fits(max_size: usize, size: f64, times: usize) -> bool { + times as f64 * size <= max_size as f64 } -``` - -And its output: - -```sh -$ cargo run -false -true -false -true -$ -``` -*/ -pub mod areas_volumes; -pub use areas_volumes::*; +#[inline] pub fn area_fit( - x: usize, - y: usize, - objects: GeometricalShapes, + (x, y): (usize, usize), + kind: GeometricalShapes, times: usize, - a: usize, - b: usize, + (a, b): (usize, usize), ) -> bool { - let max_size = x * y; - let size; - match objects { - GeometricalShapes::Square => size = square_area(a) as f64, - GeometricalShapes::Circle => size = circle_area(a), - GeometricalShapes::Rectangle => size = rectangle_area(a, b) as f64, - GeometricalShapes::Triangle => size = triangle_area(a, b), - } - times as f64 * size <= max_size as f64 + generic_fits( + x * y, + match kind { + GeometricalShapes::Square => square_area(a) as _, + GeometricalShapes::Circle => circle_area(a), + GeometricalShapes::Rectangle => rectangle_area(a, b) as _, + GeometricalShapes::Triangle => triangle_area(a, b), + }, + times, + ) } +#[inline] pub fn volume_fit( - x: usize, - y: usize, - z: usize, - objects: GeometricalVolumes, + (x, y, z): (usize, usize, usize), + kind: GeometricalVolumes, times: usize, - a: usize, - b: usize, - c: usize, + (a, b, c): (usize, usize, usize), ) -> bool { - let max_size = x * y * z; - let size; - match objects { - GeometricalVolumes::Cube => size = cube_volume(a) as f64, - GeometricalVolumes::Sphere => size = sphere_volume(a), - GeometricalVolumes::Parallelepiped => size = parallelepiped_volume(a, b, c) as f64, - GeometricalVolumes::Pyramid => size = triangular_pyramid_volume(triangle_area(a, b), c), - GeometricalVolumes::Cone => size = cone_volume(a, b), - } - times as f64 * size <= max_size as f64 -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn no_volumes_shapes() { - assert_eq!(true, area_fit(2, 5, GeometricalShapes::Circle, 0, 2, 1)); - assert_eq!(true, area_fit(2, 2, GeometricalShapes::Rectangle, 0, 6, 10)); - assert_eq!( - true, - volume_fit(2, 5, 3, GeometricalVolumes::Cone, 0, 1, 1, 1) - ); - assert_eq!( - true, - volume_fit(3, 5, 7, GeometricalVolumes::Parallelepiped, 0, 2, 6, 3) - ); - } - - #[test] - fn equal_size() { - assert_eq!(true, area_fit(2, 5, GeometricalShapes::Square, 1, 2, 5)); - assert_eq!( - true, - volume_fit(3, 1, 4, GeometricalVolumes::Cube, 1, 1, 3, 4) - ); - } - - #[test] - fn all_shapes() { - assert_eq!(false, area_fit(3, 5, GeometricalShapes::Circle, 2, 2, 0)); - assert_eq!(true, area_fit(8, 6, GeometricalShapes::Triangle, 5, 5, 2)); - assert_eq!(true, area_fit(7, 3, GeometricalShapes::Rectangle, 2, 2, 4)); - assert_eq!(true, area_fit(5, 5, GeometricalShapes::Square, 1, 2, 4)); - } - - #[test] - fn all_volumes() { - assert_eq!( - true, - volume_fit(5, 6, 3, GeometricalVolumes::Cube, 2, 3, 3, 4) - ); - assert_eq!( - false, - volume_fit(7, 4, 4, GeometricalVolumes::Cone, 1, 8, 2, 0) - ); - assert_eq!( - true, - volume_fit(2, 5, 3, GeometricalVolumes::Sphere, 1, 1, 1, 1) - ); - assert_eq!( - false, - volume_fit(2, 5, 3, GeometricalVolumes::Parallelepiped, 31, 1, 1, 1) - ); - assert_eq!( - true, - volume_fit(7, 5, 3, GeometricalVolumes::Pyramid, 3, 3, 2, 1) - ); - } + generic_fits( + x * y * z, + match kind { + GeometricalVolumes::Cube => cube_volume(a) as _, + GeometricalVolumes::Sphere => sphere_volume(a), + GeometricalVolumes::Parallelepiped => parallelepiped_volume(a, b, c) as _, + GeometricalVolumes::TriangularPyramid => { + triangular_pyramid_volume(triangle_area(a, b), c) + } + GeometricalVolumes::Cone => cone_volume(a, b), + }, + times, + ) } diff --git a/solutions/middle_day/Cargo.toml b/solutions/middle_day/Cargo.toml index 2775048f..90bc0040 100644 --- a/solutions/middle_day/Cargo.toml +++ b/solutions/middle_day/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -chrono="0.4.19" \ No newline at end of file +chrono = "0.4.40" diff --git a/solutions/middle_day/src/lib.rs b/solutions/middle_day/src/lib.rs index ea744810..d31bcaf9 100644 --- a/solutions/middle_day/src/lib.rs +++ b/solutions/middle_day/src/lib.rs @@ -1,37 +1,15 @@ -extern crate chrono; -pub use chrono::prelude::*; -pub use chrono::Weekday as wd; +use chrono::{Datelike, NaiveDate, Weekday}; -pub fn middle_day(year: usize) -> Option { - if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) { - return None; - } - - Some( - Utc.with_ymd_and_hms(year as i32, 7, 2, 0, 0, 0) - .unwrap() - .weekday(), - ) -} +const MIDDLE_OF_YEAR: (u32, u32) = (7, 2); -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn leap_years() { - assert!(middle_day(1892).is_none(), "1892 was a leap year!"); - assert!(middle_day(1904).is_none(), "1904 was a leap year!"); - assert!(middle_day(2012).is_none(), "2012 was a leap year!"); - } - - #[test] - fn weekdays() { - assert_eq!(wd::Tue, middle_day(2019).unwrap()); - assert_eq!(wd::Wed, middle_day(1997).unwrap()); - assert_eq!(wd::Mon, middle_day(1663).unwrap()); - assert_eq!(wd::Wed, middle_day(1873).unwrap()); - assert_eq!(wd::Thu, middle_day(1953).unwrap()); - assert_eq!(wd::Wed, middle_day(1879).unwrap()); +pub fn middle_day(year: u32) -> Option { + if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) { + None + } else { + Some( + NaiveDate::from_ymd_opt(year.try_into().unwrap(), MIDDLE_OF_YEAR.0, MIDDLE_OF_YEAR.1) + .unwrap() + .weekday(), + ) } } From f05863a71e1b15e58f96cc217f84a75400d83b7c Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 17 Mar 2025 17:06:49 +0000 Subject: [PATCH 14/26] fix(middle_day && does_it_fit) --- tests/does_it_fit_test/src/areas_volumes.rs | 50 ------------- tests/does_it_fit_test/src/lib.rs | 72 ++++++++++++++++++ tests/does_it_fit_test/src/main.rs | 81 --------------------- tests/middle_day_test/Cargo.toml | 1 + tests/middle_day_test/src/lib.rs | 19 +++++ tests/middle_day_test/src/main.rs | 32 -------- 6 files changed, 92 insertions(+), 163 deletions(-) delete mode 100644 tests/does_it_fit_test/src/areas_volumes.rs create mode 100644 tests/does_it_fit_test/src/lib.rs delete mode 100644 tests/does_it_fit_test/src/main.rs create mode 100644 tests/middle_day_test/src/lib.rs delete mode 100644 tests/middle_day_test/src/main.rs diff --git a/tests/does_it_fit_test/src/areas_volumes.rs b/tests/does_it_fit_test/src/areas_volumes.rs deleted file mode 100644 index 431179b4..00000000 --- a/tests/does_it_fit_test/src/areas_volumes.rs +++ /dev/null @@ -1,50 +0,0 @@ -pub enum GeometricalShapes { - Square, - Circle, - Rectangle, - Triangle, -} - -pub enum GeometricalVolumes { - Cube, - Sphere, - Cone, - Pyramid, - Parallelepiped, -} - -pub fn square_area(side: usize) -> usize { - side.pow(2) -} - -pub fn triangle_area(base: usize, height: usize) -> f64 { - (base as f64 * height as f64) / 2.0 -} - -pub fn circle_area(radius: usize) -> f64 { - std::f64::consts::PI * (radius.pow(2) as f64) -} - -pub fn rectangle_area(side_a: usize, side_b: usize) -> usize { - side_a * side_b -} - -pub fn cube_volume(side: usize) -> usize { - side.pow(3) -} - -pub fn sphere_volume(radius: usize) -> f64 { - (4.0 / 3.0) * std::f64::consts::PI * (radius.pow(3) as f64) -} - -pub fn triangular_pyramid_volume(base_area: f64, height: usize) -> f64 { - (base_area * height as f64) / 3.0 -} - -pub fn parallelepiped_volume(side_a: usize, side_b: usize, side_c: usize) -> usize { - side_a * side_b * side_c -} - -pub fn cone_volume(base_radius: usize, height: usize) -> f64 { - (1.0 / 3.0) * std::f64::consts::PI * base_radius.pow(2) as f64 * height as f64 -} diff --git a/tests/does_it_fit_test/src/lib.rs b/tests/does_it_fit_test/src/lib.rs new file mode 100644 index 00000000..8f198f87 --- /dev/null +++ b/tests/does_it_fit_test/src/lib.rs @@ -0,0 +1,72 @@ +use does_it_fit::*; + +#[test] +fn no_volumes_shapes() { + assert!(area_fit((2, 5), GeometricalShapes::Circle, 0, (2, 1))); + assert!(area_fit((2, 2), GeometricalShapes::Rectangle, 0, (6, 10))); + assert!(volume_fit( + (2, 5, 3), + GeometricalVolumes::Cone, + 0, + (1, 1, 1) + )); + assert!(volume_fit( + (3, 5, 7), + GeometricalVolumes::Parallelepiped, + 0, + (2, 6, 3) + )); +} + +#[test] +fn equal_size() { + assert!(area_fit((2, 5), GeometricalShapes::Square, 1, (2, 5))); + assert!(volume_fit( + (3, 1, 4), + GeometricalVolumes::Cube, + 1, + (1, 3, 4) + )); +} + +#[test] +fn all_shapes() { + assert!(!area_fit((3, 5), GeometricalShapes::Circle, 2, (2, 0))); + assert!(area_fit((8, 6), GeometricalShapes::Triangle, 5, (5, 2))); + assert!(area_fit((7, 3), GeometricalShapes::Rectangle, 2, (2, 4))); + assert!(area_fit((5, 5), GeometricalShapes::Square, 1, (2, 4))); +} + +#[test] +fn all_volumes() { + assert!(volume_fit( + (5, 6, 3), + GeometricalVolumes::Cube, + 2, + (3, 3, 4) + )); + assert!(!volume_fit( + (7, 4, 4), + GeometricalVolumes::Cone, + 1, + (8, 2, 0) + )); + assert!(volume_fit( + (2, 5, 3), + GeometricalVolumes::Sphere, + 1, + (1, 1, 1) + )); + assert!(!volume_fit( + (2, 5, 3), + GeometricalVolumes::Parallelepiped, + 31, + (1, 1, 1) + )); + assert!(volume_fit( + (7, 5, 3), + GeometricalVolumes::TriangularPyramid, + 3, + (3, 2, 1) + )); +} diff --git a/tests/does_it_fit_test/src/main.rs b/tests/does_it_fit_test/src/main.rs deleted file mode 100644 index 38985f3e..00000000 --- a/tests/does_it_fit_test/src/main.rs +++ /dev/null @@ -1,81 +0,0 @@ -use does_it_fit::*; - -fn main() { - println!( - "Does 100 rectangles (2x1) fit in a 2 by 4 square? {}", - area_fit(2, 4, GeometricalShapes::Rectangle, 100, 2, 1) - ); - println!( - "Does 3 triangles (5 base and 3 height) fit in a 5 by 5 square? {}", - area_fit(5, 5, GeometricalShapes::Triangle, 3, 5, 3) - ); - println!( - "Does 3 spheres (2 radius) fit in a 5 by 5 by 5 box? {}", - volume_fit(5, 5, 5, GeometricalVolumes::Sphere, 3, 2, 0, 0) - ); - println!( - "Does 3 triangles (5 base and 3 height) fit in a 5 by 7 by 5 box? {}", - volume_fit(5, 7, 5, GeometricalVolumes::Parallelepiped, 1, 6, 7, 4) - ); -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn no_volumes_shapes() { - assert_eq!(true, area_fit(2, 5, GeometricalShapes::Circle, 0, 2, 1)); - assert_eq!(true, area_fit(2, 2, GeometricalShapes::Rectangle, 0, 6, 10)); - assert_eq!( - true, - volume_fit(2, 5, 3, GeometricalVolumes::Cone, 0, 1, 1, 1) - ); - assert_eq!( - true, - volume_fit(3, 5, 7, GeometricalVolumes::Parallelepiped, 0, 2, 6, 3) - ); - } - - #[test] - fn equal_size() { - assert_eq!(true, area_fit(2, 5, GeometricalShapes::Square, 1, 2, 5)); - assert_eq!( - true, - volume_fit(3, 1, 4, GeometricalVolumes::Cube, 1, 1, 3, 4) - ); - } - - #[test] - fn all_shapes() { - assert_eq!(false, area_fit(3, 5, GeometricalShapes::Circle, 2, 2, 0)); - assert_eq!(true, area_fit(8, 6, GeometricalShapes::Triangle, 5, 5, 2)); - assert_eq!(true, area_fit(7, 3, GeometricalShapes::Rectangle, 2, 2, 4)); - assert_eq!(true, area_fit(5, 5, GeometricalShapes::Square, 1, 2, 4)); - } - - #[test] - fn all_volumes() { - assert_eq!( - true, - volume_fit(5, 6, 3, GeometricalVolumes::Cube, 2, 3, 3, 4) - ); - assert_eq!( - false, - volume_fit(7, 4, 4, GeometricalVolumes::Cone, 1, 8, 2, 0) - ); - assert_eq!( - true, - volume_fit(2, 5, 3, GeometricalVolumes::Sphere, 1, 1, 1, 1) - ); - assert_eq!( - false, - volume_fit(2, 5, 3, GeometricalVolumes::Parallelepiped, 31, 1, 1, 1) - ); - assert_eq!( - true, - volume_fit(7, 5, 3, GeometricalVolumes::Pyramid, 3, 3, 2, 1) - ); - } -} diff --git a/tests/middle_day_test/Cargo.toml b/tests/middle_day_test/Cargo.toml index 78d40f91..932efd84 100644 --- a/tests/middle_day_test/Cargo.toml +++ b/tests/middle_day_test/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chrono = "0.4.40" middle_day = { path = "../../solutions/middle_day"} diff --git a/tests/middle_day_test/src/lib.rs b/tests/middle_day_test/src/lib.rs new file mode 100644 index 00000000..acc85f08 --- /dev/null +++ b/tests/middle_day_test/src/lib.rs @@ -0,0 +1,19 @@ +use chrono::Weekday; +use middle_day::*; + +#[test] +fn leap_years() { + assert!(middle_day(1892).is_none(), "1892 was a leap year!"); + assert!(middle_day(1904).is_none(), "1904 was a leap year!"); + assert!(middle_day(2012).is_none(), "2012 was a leap year!"); +} + +#[test] +fn weekdays() { + assert_eq!(Weekday::Tue, middle_day(2019).unwrap()); + assert_eq!(Weekday::Wed, middle_day(1997).unwrap()); + assert_eq!(Weekday::Mon, middle_day(1663).unwrap()); + assert_eq!(Weekday::Wed, middle_day(1873).unwrap()); + assert_eq!(Weekday::Thu, middle_day(1953).unwrap()); + assert_eq!(Weekday::Wed, middle_day(1879).unwrap()); +} diff --git a/tests/middle_day_test/src/main.rs b/tests/middle_day_test/src/main.rs deleted file mode 100644 index 0d238529..00000000 --- a/tests/middle_day_test/src/main.rs +++ /dev/null @@ -1,32 +0,0 @@ -use middle_day::*; - -fn main() { - println!("{:?}", middle_day(1022).unwrap()); -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn leap_years() { - println!( - "{:?}", - (middle_day(1892), middle_day(1904), middle_day(2012)) - ); - assert!(middle_day(1892).is_none(), "1892 was a leap year!"); - assert!(middle_day(1904).is_none(), "1904 was a leap year!"); - assert!(middle_day(2012).is_none(), "2012 was a leap year!"); - } - - #[test] - fn weekdays() { - assert_eq!(wd::Tue, middle_day(2019).unwrap()); - assert_eq!(wd::Wed, middle_day(1997).unwrap()); - assert_eq!(wd::Mon, middle_day(1663).unwrap()); - assert_eq!(wd::Wed, middle_day(1873).unwrap()); - assert_eq!(wd::Thu, middle_day(1953).unwrap()); - assert_eq!(wd::Wed, middle_day(1879).unwrap()); - } -} From afa30dabfc2661867cda137f20b7ad7bf49a9242 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 17 Mar 2025 17:47:30 +0000 Subject: [PATCH 15/26] fix(macro_calculator) --- tests/macro_calculator_test/src/main.rs | 127 +++++++++--------------- 1 file changed, 49 insertions(+), 78 deletions(-) diff --git a/tests/macro_calculator_test/src/main.rs b/tests/macro_calculator_test/src/main.rs index 3e3f749e..59bfd35c 100644 --- a/tests/macro_calculator_test/src/main.rs +++ b/tests/macro_calculator_test/src/main.rs @@ -1,94 +1,65 @@ -extern crate json; use macro_calculator::*; -fn main() { - let foods = vec![ +#[test] +fn testing_macros_values() { + let a = Food { + name: "light milk".to_owned(), + calories: ("148kJ".to_owned(), "35kcal".to_owned()), + proteins: 3.5, + fats: 0.1, + carbs: 5.0, + nbr_of_portions: 0.7, + }; + let b = Food { + name: "oat cookies".to_owned(), + calories: ("1996kJ".to_owned(), "477kcal".to_owned()), + proteins: 8.2, + fats: 21.0, + carbs: 60.4, + nbr_of_portions: 1.2, + }; + + let macros = calculate_macros(&[a, b]); + + assert_eq!(macros["cals"].as_f64().unwrap(), 596.9); + assert_eq!(macros["carbs"].as_f64().unwrap(), 75.98); + assert_eq!(macros["proteins"].as_f64().unwrap(), 12.29); + assert_eq!(macros["fats"].as_f64().unwrap(), 25.27); +} + +#[test] +fn testing_no_food() { + let macros = calculate_macros(&[]); + + assert_eq!(macros["cals"].as_f64().unwrap(), 0.0); + assert_eq!(macros["carbs"].as_f64().unwrap(), 0.0); + assert_eq!(macros["proteins"].as_f64().unwrap(), 0.0); + assert_eq!(macros["fats"].as_f64().unwrap(), 0.0); +} + +#[test] +fn big_values() { + let macros = calculate_macros(&[ Food { - name: String::from("big mac"), - calories: ["2133.84kJ".to_string(), "510kcal".to_string()], + name: "big mac".to_owned(), + calories: ("2133.84kJ".to_owned(), "510kcal".to_owned()), proteins: 27.0, fats: 26.0, carbs: 41.0, nbr_of_portions: 2.0, }, Food { - name: "pizza margherita".to_string(), - calories: ["1500.59kJ".to_string(), "358.65kcal".to_string()], + name: "pizza margherita".to_owned(), + calories: ("1500.59kJ".to_owned(), "358.65kcal".to_owned()), proteins: 13.89, fats: 11.21, carbs: 49.07, nbr_of_portions: 4.9, }, - ]; - - println!("{:#}", calculate_macros(foods)); -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn testing_macros_values() { - let a = Food { - name: "light milk".to_string(), - calories: ["148kJ".to_string(), "35kcal".to_string()], - proteins: 3.5, - fats: 0.1, - carbs: 5.0, - nbr_of_portions: 0.7, - }; - let b = Food { - name: "oat cookies".to_string(), - calories: ["1996kJ".to_string(), "477kcal".to_string()], - proteins: 8.2, - fats: 21.0, - carbs: 60.4, - nbr_of_portions: 1.2, - }; - - let macros = calculate_macros(vec![a, b]); - - assert_eq!(macros["cals"].as_f64().unwrap(), 596.9); - assert_eq!(macros["carbs"].as_f64().unwrap(), 75.98); - assert_eq!(macros["proteins"].as_f64().unwrap(), 12.29); - assert_eq!(macros["fats"].as_f64().unwrap(), 25.27); - } - - #[test] - fn testing_no_food() { - let macros = calculate_macros(vec![]); - - assert_eq!(macros["cals"].as_f64().unwrap(), 0.0); - assert_eq!(macros["carbs"].as_f64().unwrap(), 0.0); - assert_eq!(macros["proteins"].as_f64().unwrap(), 0.0); - assert_eq!(macros["fats"].as_f64().unwrap(), 0.0); - } - - #[test] - fn big_values() { - let macros = calculate_macros(vec![ - Food { - name: "big mac".to_string(), - calories: ["2133.84kJ".to_string(), "510kcal".to_string()], - proteins: 27.0, - fats: 26.0, - carbs: 41.0, - nbr_of_portions: 2.0, - }, - Food { - name: "pizza margherita".to_string(), - calories: ["1500.59kJ".to_string(), "358.65kcal".to_string()], - proteins: 13.89, - fats: 11.21, - carbs: 49.07, - nbr_of_portions: 4.9, - }, - ]); + ]); - assert_eq!(macros["cals"].as_f64().unwrap(), 2777.39); - assert_eq!(macros["carbs"].as_f64().unwrap(), 322.44); - assert_eq!(macros["proteins"].as_f64().unwrap(), 122.06); - assert_eq!(macros["fats"].as_f64().unwrap(), 106.93); - } + assert_eq!(macros["cals"].as_f64().unwrap(), 2777.39); + assert_eq!(macros["carbs"].as_f64().unwrap(), 322.44); + assert_eq!(macros["proteins"].as_f64().unwrap(), 122.06); + assert_eq!(macros["fats"].as_f64().unwrap(), 106.93); } From 7c1c6edafc2b297efe00aacbb2bb755fa533da31 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 17 Mar 2025 17:47:37 +0000 Subject: [PATCH 16/26] fix(macro_calculator) --- solutions/macro_calculator/src/lib.rs | 177 +++----------------------- 1 file changed, 18 insertions(+), 159 deletions(-) diff --git a/solutions/macro_calculator/src/lib.rs b/solutions/macro_calculator/src/lib.rs index 7203c2c8..1e740a3d 100644 --- a/solutions/macro_calculator/src/lib.rs +++ b/solutions/macro_calculator/src/lib.rs @@ -1,174 +1,33 @@ -/* -## macro_calculator - -### Instructions - -Create a function `calculate_macros` that receives a vector of `serde_json::Value` and returns a `serde_json::Value`. - -The vector you will receive will contain jsons in the following format: - -```json -{ - "name": , - "calories": [, ], - "fats": , - "carbs": , - "proteins": , - "nbr_of_portions": -} -``` - -Besides the name and the content of the calories array, that are strings, everything else are floats. - -As the result of the function you should return a json with the following format (Macros struct): - -```json - "cals": , - "carbs": , - "proteins": , - "fats": , -``` - -The number of portions should be taken into account. The values of the macros refer to one portion. -All values should be floats (f64) and should be the addition of every macronutrient in the provided array (cals is the addition of every calories, fats is the addition of every fats, and so on...). -Every value should be rounded to two decimal places (ex: 12.29) or one decimal place if it ends in 0 (ex: 18.90 -> 18.9). - -Hint: You will need the `serde`, `serde_json` and `serde_derive` crates. - -### Example - -```rust -fn main() { - let a = vec![ - Food { - name: String::from("big mac"), - calories: ["2133.84kJ".to_string(), "510kcal".to_string()], - protein: 27.0, - fats: 26.0, - carbs: 41.0, - nbr_of_portions: 2.0, - }, - Food { - name: "pizza margherita".to_string(), - calories: ["1500.59kJ".to_string(), "358.65kcal".to_string()], - protein: 13.89, - fats: 11.21, - carbs: 49.07, - nbr_of_portions: 4.9, - }, - ]; - - println!("{:#}", calculate_macros(a)); -} -``` - -*/ - -extern crate json; +use std::array; use json::object; pub struct Food { - #[allow(dead_code)] pub name: String, - pub calories: [String; 2], - pub proteins: f64, + pub calories: (String, String), pub fats: f64, pub carbs: f64, + pub proteins: f64, pub nbr_of_portions: f64, } -pub fn calculate_macros(foods: Vec) -> json::JsonValue { - let (mut cals, mut prot, mut carbs, mut fats) = (0.0, 0.0, 0.0, 0.0); +pub fn calculate_macros(foods: &[Food]) -> json::JsonValue { + let [kcals, fats, carbs, proteins] = foods + .iter() + .map(|f| { + let kcals = f.calories.1.split_terminator("kcal").next().unwrap(); + let kcals = kcals.parse().unwrap(); - for food in foods { - let cal = &food.calories[1] - .to_string() - .split("kcal") - .collect::>()[0] - .to_string() - .parse::() - .unwrap(); - - cals += cal * food.nbr_of_portions; - prot += food.proteins * food.nbr_of_portions; - carbs += food.carbs * food.nbr_of_portions; - fats += food.fats * food.nbr_of_portions; - } + [kcals, f.fats, f.carbs, f.proteins].map(|v| v * f.nbr_of_portions) + }) + .reduce(|acc, next| array::from_fn(|i| acc[i] + next[i])) + .unwrap_or_default() + .map(|v| (v * 100.0).round() / 100.0); object! { - "cals": (cals * 100.0).round() / 100.0, - "carbs": (carbs * 100.0).round() / 100.0, - "proteins": (prot * 100.0).round() / 100.0, - "fats": (fats * 100.0).round() / 100.0, - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn testing_macros_values() { - let a = Food { - name: "light milk".to_string(), - calories: ["148kJ".to_string(), "35kcal".to_string()], - proteins: 3.5, - fats: 0.1, - carbs: 5.0, - nbr_of_portions: 0.7, - }; - let b = Food { - name: "oat cookies".to_string(), - calories: ["1996kJ".to_string(), "477kcal".to_string()], - proteins: 8.2, - fats: 21.0, - carbs: 60.4, - nbr_of_portions: 1.2, - }; - - let macros = calculate_macros(vec![a, b]); - - assert_eq!(macros["cals"], 596.9); - assert_eq!(macros["carbs"], 75.98); - assert_eq!(macros["proteins"], 12.29); - assert_eq!(macros["fats"], 25.27); - } - - #[test] - fn testing_no_food() { - let macros = calculate_macros(vec![]); - - assert_eq!(macros["cals"], 0.0); - assert_eq!(macros["carbs"], 0.0); - assert_eq!(macros["proteins"], 0.0); - assert_eq!(macros["fats"], 0.0); - } - - #[test] - fn big_values() { - let macros = calculate_macros(vec![ - Food { - name: "big mac".to_string(), - calories: ["2133.84kJ".to_string(), "510kcal".to_string()], - proteins: 27.0, - fats: 26.0, - carbs: 41.0, - nbr_of_portions: 2.0, - }, - Food { - name: "pizza margherita".to_string(), - calories: ["1500.59kJ".to_string(), "358.65kcal".to_string()], - proteins: 13.89, - fats: 11.21, - carbs: 49.07, - nbr_of_portions: 4.9, - }, - ]); - - assert_eq!(macros["cals"].as_f64().unwrap(), 2777.39); - assert_eq!(macros["carbs"].as_f64().unwrap(), 322.44); - assert_eq!(macros["proteins"].as_f64().unwrap(), 122.06); - assert_eq!(macros["fats"].as_f64().unwrap(), 106.93); + cals: kcals, + carbs: carbs, + proteins: proteins, + fats: fats } } From 0591d2c6458c294a0666f6410c74849d0600fbd3 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 19 Mar 2025 12:17:33 +0000 Subject: [PATCH 17/26] fix(shopping_mall) --- .../own_and_return_test/Cargo.toml | 0 .../own_and_return_test/src/main.rs | 0 tests/shopping_mall_test/src/lib.rs | 208 ++++++++++ tests/shopping_mall_test/src/main.rs | 379 ------------------ tests/shopping_mall_test/src/mall.rs | 377 +++++++++++++++++ 5 files changed, 585 insertions(+), 379 deletions(-) rename tests/{ => pig_latin_test}/own_and_return_test/Cargo.toml (100%) rename tests/{ => pig_latin_test}/own_and_return_test/src/main.rs (100%) create mode 100644 tests/shopping_mall_test/src/lib.rs delete mode 100644 tests/shopping_mall_test/src/main.rs create mode 100644 tests/shopping_mall_test/src/mall.rs diff --git a/tests/own_and_return_test/Cargo.toml b/tests/pig_latin_test/own_and_return_test/Cargo.toml similarity index 100% rename from tests/own_and_return_test/Cargo.toml rename to tests/pig_latin_test/own_and_return_test/Cargo.toml diff --git a/tests/own_and_return_test/src/main.rs b/tests/pig_latin_test/own_and_return_test/src/main.rs similarity index 100% rename from tests/own_and_return_test/src/main.rs rename to tests/pig_latin_test/own_and_return_test/src/main.rs diff --git a/tests/shopping_mall_test/src/lib.rs b/tests/shopping_mall_test/src/lib.rs new file mode 100644 index 00000000..f0c90095 --- /dev/null +++ b/tests/shopping_mall_test/src/lib.rs @@ -0,0 +1,208 @@ +mod mall; + +use std::collections::HashMap; + +use mall::*; +use shopping_mall::*; + +fn employees(mall: &mut Mall) -> HashMap { + mall.floors + .values_mut() + .flat_map(|f| { + f.stores + .values_mut() + .flat_map(|s| s.employees.iter_mut().map(|(k, v)| (k.clone(), v))) + }) + .collect::>() +} + +#[test] +fn biggest_store_tests() { + let mut mall = mall(); + + assert!(matches!( + biggest_store(&mall), + ( + name, + Store { + square_meters: 950, + .. + } + ) if name == "Pretail" + )); + + mall.floors + .get_mut("Supermarket") + .unwrap() + .remove_store("Pretail"); + + assert!(matches!( + biggest_store(&mall), + ( + name, + Store { + square_meters: 60, + .. + } + ) if name == "PizBite" + )); +} + +#[test] +fn highest_paid_test() { + let mut mall = mall(); + + let highest_paid = highest_paid_employee(&mall); + + assert!(matches!( + highest_paid[..], + [(name, Employee { age: 54, .. })] if name == "Abdallah Stafford" + )); + + let highest_salary = highest_paid[0].1.salary; + + let mut employees = employees(&mut mall); + + let another_employee = employees.get_mut("Finbar Haines").unwrap(); + + another_employee.raise(highest_salary - another_employee.salary); + + let highest_paid = highest_paid_employee(&mall); + + assert_eq!(2, highest_paid.len()); + assert!(highest_paid + .windows(2) + .all(|w| w[0] != w[1] && w[0].1.salary == w[1].1.salary)); + assert!(highest_paid.into_iter().all( + |v| matches!(v, (n, Employee { age: 54, .. }) if n == "Abdallah Stafford") + | matches!(v, (n, Employee { age: 36, .. }) if n == "Finbar Haines") + )); +} + +#[test] +fn nbr_of_employees_test() { + let mut mall = mall(); + + assert_eq!(36, nbr_of_employees(&mall)); + + mall.floors + .get_mut("Supermarket") + .unwrap() + .stores + .get_mut("Pretail") + .unwrap() + .employees + .drain(); + + assert_eq!(22, nbr_of_employees(&mall)); +} + +#[test] +fn check_for_securities_test() { + let mut mall = mall(); + + assert_eq!(3, mall.guards.len()); + + check_for_securities( + &mut mall, + [ + ( + "Peter Solomons", + Guard { + age: 45, + years_experience: 20, + }, + ), + ( + "William Charles", + Guard { + age: 32, + years_experience: 10, + }, + ), + ( + "Leonardo Changretta", + Guard { + age: 23, + years_experience: 0, + }, + ), + ( + "Vlad Levi", + Guard { + age: 38, + years_experience: 8, + }, + ), + ( + "Faruk Berkai", + Guard { + age: 40, + years_experience: 15, + }, + ), + ( + "Chritopher Smith", + Guard { + age: 35, + years_experience: 9, + }, + ), + ( + "Jason Mackie", + Guard { + age: 26, + years_experience: 2, + }, + ), + ( + "Kenzie Mair", + Guard { + age: 34, + years_experience: 8, + }, + ), + ( + "Bentley Larson", + Guard { + age: 33, + years_experience: 10, + }, + ), + ( + "Ray Storey", + Guard { + age: 37, + years_experience: 12, + }, + ), + ] + .map(|(n, d)| (n.to_owned(), d)) + .into(), + ); + + assert_eq!(9, mall.guards.len()); +} + +#[test] +fn cut_or_raise_test() { + let mut mall = mall(); + + cut_or_raise(&mut mall); + + { + let employees = employees(&mut mall); + + assert_eq!(585.792, employees.get("Finbar Haines").unwrap().salary); + assert_eq!(1100.473, employees.get("Sienna-Rose Penn").unwrap().salary); + } + + cut_or_raise(&mut mall); + + { + let employees = employees(&mut mall); + + assert_eq!(527.2128, employees.get("Finbar Haines").unwrap().salary); + assert_eq!(1210.5203, employees.get("Sienna-Rose Penn").unwrap().salary); + } +} diff --git a/tests/shopping_mall_test/src/main.rs b/tests/shopping_mall_test/src/main.rs deleted file mode 100644 index 5ab22961..00000000 --- a/tests/shopping_mall_test/src/main.rs +++ /dev/null @@ -1,379 +0,0 @@ -/* -## shopping_mall - -### Instructions - -You will have to create several functions to help run a shopping mall, with the help of the `mall` module provided: - -- `biggest_store`: receives a `mall::Mall` and returns the `Store` with the biggest `square_meters`; -- `highest_paid_employees`: receives a `mall::Mall` and returns a vector containing the `Employee`(s) with the highest salaries; -- `nbr_of_employees`: receives a `mall::Mall` and returns the number of employees and securities, as an `usize`, in that mall. -- `fire_old_securities`: receives a `mall::Mall` and removes from the `mall::Mall.securities` all securities who are 50 years old or older. -- `check_securities`: receives a `mall::Mall` and a vector of `Security` and if there are not at least 1 security for every 200 square meters of floor size, there should be added a security to the `mall::Mall.securities` -- `cut_or_raise`: receives a `mall::Mall and raises or cuts the salary of every employee in the mall by 10% depending if the employee works for more than 10 hours - -### Expected Functions (and Structures) - -```rust -pub fn biggest_store(mall: mall::Mall) -> store::Store {} - -pub fn highest_paid_employee(mall: mall::Mall) -> Vec {} - -pub fn nbr_of_employees(mall: mall::Mall) -> usize {} - -pub fn fire_old_securities(mall: &mut mall::Mall) {} - -pub fn check_for_securities(mall: &mut mall::Mall, available_sec: Vec) {} - -pub fn cut_or_raise(mall: &mut mall::Mall) {} -``` - -### Usage - -Here is a program to test your function: - -```rust -fn main() { - let secs = vec![ - mall::security::Security::new("John Oliver", 34, 7), - mall::security::Security::new("Logan West", 23, 2), - mall::security::Security::new("Bob Schumacher", 53, 15), - ]; - - let footzo_emp = vec![ - mall::floor::store::employee::Employee::new("Finbar Haines", 36, 9, 14, 650.88), - mall::floor::store::employee::Employee::new("Roksanna Rocha", 45, 13, 22, 772.00), - mall::floor::store::employee::Employee::new("Sienna-Rose Penn", 26, 9, 22, 1000.43), - ]; - let swashion_emp = vec![ - mall::floor::store::employee::Employee::new("Abdallah Stafford", 54, 8, 22, 1234.21), - mall::floor::store::employee::Employee::new("Marian Snyder", 21, 8, 14, 831.90), - mall::floor::store::employee::Employee::new("Amanda Mclean", 29, 13, 22, 1222.12), - mall::floor::store::employee::Employee::new("Faizaan Castro", 32, 11, 18, 1106.43), - ]; - let pizbite_emp = vec![ - mall::floor::store::employee::Employee::new("Juniper Cannon", 21, 16, 23, 804.35), - mall::floor::store::employee::Employee::new("Alena Simon", 28, 9, 15, 973.54), - mall::floor::store::employee::Employee::new("Yasemin Collins", 29, 9, 19, 986.33), - mall::floor::store::employee::Employee::new("Areeb Roberson", 54, 9, 22, 957.82), - mall::floor::store::employee::Employee::new("Rocco Amin", 44, 13, 23, 689.21), - ]; - let grill_emp = vec![ - mall::floor::store::employee::Employee::new("Rhian Crowther", 45, 9, 15, 841.18), - mall::floor::store::employee::Employee::new("Nikkita Steadman", 52, 14, 22, 858.61), - mall::floor::store::employee::Employee::new("Reginald Poole", 32, 9, 22, 1197.64), - mall::floor::store::employee::Employee::new("Minnie Bull", 54, 14, 22, 1229.73), - ]; - let sumo_emp = vec![ - mall::floor::store::employee::Employee::new("Chantelle Barajas", 20, 8, 22, 969.22), - mall::floor::store::employee::Employee::new("Hywel Rudd", 49, 12, 22, 695.74), - mall::floor::store::employee::Employee::new("Marianne Beasley", 55, 8, 14, 767.83), - ]; - let supermaket_emp = vec![ - mall::floor::store::employee::Employee::new("Amara Schaefer", 23, 9, 14, 796.21), - mall::floor::store::employee::Employee::new("Yara Wickens", 39, 9, 14, 853.42), - mall::floor::store::employee::Employee::new("Tomi Boyer", 64, 9, 14, 881.83), - mall::floor::store::employee::Employee::new("Greta Dickson", 42, 9, 14, 775.10), - mall::floor::store::employee::Employee::new("Caroline Finnegan", 41, 9, 14, 702.92), - mall::floor::store::employee::Employee::new("Indiana Baxter", 33, 13, 20, 991.71), - mall::floor::store::employee::Employee::new("Jadine Page", 48, 13, 20, 743.21), - mall::floor::store::employee::Employee::new("Husna Ryan", 43, 13, 20, 655.75), - mall::floor::store::employee::Employee::new("Tyler Hunt", 63, 13, 20, 668.25), - mall::floor::store::employee::Employee::new("Dahlia Caldwell", 56, 13, 20, 781.38), - mall::floor::store::employee::Employee::new("Chandler Mansell", 20, 19, 24, 656.75), - mall::floor::store::employee::Employee::new("Mohsin Mcgee", 30, 19, 24, 703.83), - mall::floor::store::employee::Employee::new("Antoine Goulding", 45, 19, 24, 697.12), - mall::floor::store::employee::Employee::new("Mark Barnard", 53, 19, 24, 788.81), - ]; - - let ground_stores = vec![ - store::Store::new("Footzo", 50, footzo_emp), - store::Store::new("Swashion", 43, swashion_emp), - ]; - let food_stores = vec![ - store::Store::new("PizBite", 60, pizbite_emp), - store::Store::new("Chillout Grill", 50, grill_emp), - store::Store::new("Sumo Food", 30, sumo_emp), - ]; - let supermarket = vec![store::Store::new("Pretail", 950, supermaket_emp)]; - - let floors = vec![ - floor::Floor::new("Ground Floor", ground_stores, 300), - floor::Floor::new("Food Floor", food_stores, 500), - floor::Floor::new("Supermarket", supermarket, 1000), - ]; - - let mall_la_vie = mall::Mall::new("La Vie Funchal", secs, floors); - - println!("{:?}", &mall_la_vie); -} -``` - - -And its output: - -```sh -$ cargo run -Mall { name: "La Vie Funchal", securities: [Security { name: "John Oliver", age: 34, years_experience: 7 }, Security { name: "Logan West", age: 23, years_experience: 2 }, Security { name: "Bob Schumacher", age: 53, years_experience: 15 }], floors: [Floor { name: "Ground Floor", stores: [Store { name: "Footzo", square_meters: 50, employees: [Employee { name: "Finbar Haines", age: 36, working_hours: (9, 14), salary: 650.88 }, Employee { name: "Roksanna Rocha", age: 45, working_hours: (13, 22), salary: 772.0 }, Employee { name: "Sienna-Rose Penn", age: 26, working_hours: (9, 22), salary: 1000.43 }] }, Store { name: "Swashion", square_meters: 43, employees: [Employee { name: "Abdallah Stafford", age: 54, working_hours: (8, 22), salary: 1234.21 }, Employee { name: "Marian Snyder", age: 21, working_hours: (8, 14), salary: 831.9 }, Employee { name: "Amanda Mclean", age: 29, working_hours: (13, 22), salary: 1222.12 }, Employee { name: "Faizaan Castro", age: 32, working_hours: (11, 18), salary: 1106.43 }] }], size_limit: 300 }, Floor { name: "Food Floor", stores: [Store { name: "PizBite", square_meters: 60, employees: [Employee { name: "Juniper Cannon", age: 21, working_hours: (16, 23), salary: 804.35 }, Employee { name: "Alena Simon", age: 28, working_hours: (9, 15), salary: 973.54 }, Employee { name: "Yasemin Collins", age: 29, working_hours: (9, 19), salary: 986.33 }, Employee { name: "Areeb Roberson", age: 54, working_hours: (9, 22), salary: 957.82 }, Employee { name: "Rocco Amin", age: 44, working_hours: (13, 23), salary: 689.21 }] }, Store { name: "Chillout Grill", square_meters: 50, employees: [Employee { name: "Rhian Crowther", age: 45, working_hours: (9, 15), salary: 841.18 }, Employee { name: "Nikkita Steadman", age: 52, working_hours: (14, 22), salary: 858.61 }, Employee { name: "Reginald Poole", age: 32, working_hours: (9, 22), salary: 1197.64 }, Employee { name: "Minnie Bull", age: 54, working_hours: (14, 22), salary: 1229.73 }] }, Store { name: "Sumo Food", square_meters: 30, employees: [Employee { name: "Chantelle Barajas", age: 20, working_hours: (8, 22), salary: 969.22 }, Employee { name: "Hywel Rudd", age: 49, working_hours: (12, 22), salary: 695.74 }, Employee { name: "Marianne Beasley", age: 55, working_hours: (8, 14), salary: 767.83 }] }], size_limit: 500 }, Floor { name: "Supermarket", stores: [Store { name: "Pretail", square_meters: 950, employees: [Employee { name: "Amara Schaefer", age: 23, working_hours: (9, 14), salary: 796.21 }, Employee { name: "Yara Wickens", age: 39, working_hours: (9, 14), salary: 853.42 }, Employee { name: "Tomi Boyer", age: 64, working_hours: (9, 14), salary: 881.83 }, Employee { name: "Greta Dickson", age: 42, working_hours: (9, 14), salary: 775.1 }, Employee { name: "Caroline Finnegan", age: 41, working_hours: (9, 14), salary: 702.92 }, Employee { name: "Indiana Baxter", age: 33, working_hours: (13, 20), salary: 991.71 }, Employee { name: "Jadine Page", age: 48, working_hours: (13, 20), salary: 743.21 }, Employee { name: "Husna Ryan", age: 43, working_hours: (13, 20), salary: 655.75 }, Employee { name: "Tyler Hunt", age: 63, working_hours: (13, 20), salary: 668.25 }, Employee { name: "Dahlia Caldwell", age: 56, working_hours: (13, 20), salary: 781.38 }, Employee { name: "Chandler Mansell", age: 20, working_hours: (19, 24), salary: 656.75 }, Employee { name: "Mohsin Mcgee", age: 30, working_hours: (19, 24), salary: 703.83 }, Employee { name: "Antoine Goulding", age: 45, working_hours: (19, 24), salary: 697.12 }, Employee { name: "Mark Barnard", age: 53, working_hours: (19, 24), salary: 788.81 }] }], size_limit: 1000 }] } -$ -} -``` -*/ - -fn main() { - let secs = vec![ - mall::guard::Guard::new("John Oliver", 34, 7), - mall::guard::Guard::new("Logan West", 23, 2), - mall::guard::Guard::new("Bob Schumacher", 53, 15), - ]; - - let footzo_emp = vec![ - mall::floor::store::employee::Employee::new("Finbar Haines", 36, 9, 14, 650.88), - mall::floor::store::employee::Employee::new("Roksanna Rocha", 45, 13, 22, 772.00), - mall::floor::store::employee::Employee::new("Sienna-Rose Penn", 26, 9, 22, 1000.43), - ]; - let swashion_emp = vec![ - mall::floor::store::employee::Employee::new("Abdallah Stafford", 54, 8, 22, 1234.21), - mall::floor::store::employee::Employee::new("Marian Snyder", 21, 8, 14, 831.90), - mall::floor::store::employee::Employee::new("Amanda Mclean", 29, 13, 22, 1222.12), - mall::floor::store::employee::Employee::new("Faizaan Castro", 32, 11, 18, 1106.43), - ]; - let pizbite_emp = vec![ - mall::floor::store::employee::Employee::new("Juniper Cannon", 21, 16, 23, 804.35), - mall::floor::store::employee::Employee::new("Alena Simon", 28, 9, 15, 973.54), - mall::floor::store::employee::Employee::new("Yasemin Collins", 29, 9, 19, 986.33), - mall::floor::store::employee::Employee::new("Areeb Roberson", 54, 9, 22, 957.82), - mall::floor::store::employee::Employee::new("Rocco Amin", 44, 13, 23, 689.21), - ]; - let grill_emp = vec![ - mall::floor::store::employee::Employee::new("Rhian Crowther", 45, 9, 15, 841.18), - mall::floor::store::employee::Employee::new("Nikkita Steadman", 52, 14, 22, 858.61), - mall::floor::store::employee::Employee::new("Reginald Poole", 32, 9, 22, 1197.64), - mall::floor::store::employee::Employee::new("Minnie Bull", 54, 14, 22, 1229.73), - ]; - let sumo_emp = vec![ - mall::floor::store::employee::Employee::new("Chantelle Barajas", 20, 8, 22, 969.22), - mall::floor::store::employee::Employee::new("Hywel Rudd", 49, 12, 22, 695.74), - mall::floor::store::employee::Employee::new("Marianne Beasley", 55, 8, 14, 767.83), - ]; - let supermaket_emp = vec![ - mall::floor::store::employee::Employee::new("Amara Schaefer", 23, 9, 14, 796.21), - mall::floor::store::employee::Employee::new("Yara Wickens", 39, 9, 14, 853.42), - mall::floor::store::employee::Employee::new("Tomi Boyer", 64, 9, 14, 881.83), - mall::floor::store::employee::Employee::new("Greta Dickson", 42, 9, 14, 775.10), - mall::floor::store::employee::Employee::new("Caroline Finnegan", 41, 9, 14, 702.92), - mall::floor::store::employee::Employee::new("Indiana Baxter", 33, 13, 20, 991.71), - mall::floor::store::employee::Employee::new("Jadine Page", 48, 13, 20, 743.21), - mall::floor::store::employee::Employee::new("Husna Ryan", 43, 13, 20, 655.75), - mall::floor::store::employee::Employee::new("Tyler Hunt", 63, 13, 20, 668.25), - mall::floor::store::employee::Employee::new("Dahlia Caldwell", 56, 13, 20, 781.38), - mall::floor::store::employee::Employee::new("Chandler Mansell", 20, 19, 24, 656.75), - mall::floor::store::employee::Employee::new("Mohsin Mcgee", 30, 19, 24, 703.83), - mall::floor::store::employee::Employee::new("Antoine Goulding", 45, 19, 24, 697.12), - mall::floor::store::employee::Employee::new("Mark Barnard", 53, 19, 24, 788.81), - ]; - - let ground_stores = vec![ - store::Store::new("Footzo", 50, footzo_emp), - store::Store::new("Swashion", 43, swashion_emp), - ]; - let food_stores = vec![ - store::Store::new("PizBite", 60, pizbite_emp), - store::Store::new("Chillout Grill", 50, grill_emp), - store::Store::new("Sumo Food", 30, sumo_emp), - ]; - let supermarket = vec![store::Store::new("Pretail", 950, supermaket_emp)]; - - let floors = vec![ - floor::Floor::new("Ground Floor", ground_stores, 300), - floor::Floor::new("Food Floor", food_stores, 500), - floor::Floor::new("Supermarket", supermarket, 1000), - ]; - - let mall_la_vie = mall::Mall::new("La Vie Funchal", secs, floors); - - println!("{:?}", &mall_la_vie); -} - -use shopping_mall::*; - -mod tests { - use super::*; - - fn create_mall() -> mall::Mall { - let secs = vec![ - mall::guard::Guard::new("John Oliver", 34, 7), - mall::guard::Guard::new("Logan West", 23, 2), - mall::guard::Guard::new("Bob Schumacher", 53, 15), - ]; - - let footzo_emp = vec![ - mall::floor::store::employee::Employee::new("Finbar Haines", 36, 9, 14, 650.88), - mall::floor::store::employee::Employee::new("Roksanna Rocha", 45, 13, 22, 772.00), - mall::floor::store::employee::Employee::new("Sienna-Rose Penn", 26, 9, 22, 1000.43), - ]; - let swashion_emp = vec![ - mall::floor::store::employee::Employee::new("Abdallah Stafford", 54, 8, 22, 1234.21), - mall::floor::store::employee::Employee::new("Marian Snyder", 21, 8, 14, 831.90), - mall::floor::store::employee::Employee::new("Amanda Mclean", 29, 13, 22, 1222.12), - mall::floor::store::employee::Employee::new("Faizaan Castro", 32, 11, 18, 1106.43), - ]; - let pizbite_emp = vec![ - mall::floor::store::employee::Employee::new("Juniper Cannon", 21, 16, 23, 804.35), - mall::floor::store::employee::Employee::new("Alena Simon", 28, 9, 15, 973.54), - mall::floor::store::employee::Employee::new("Yasemin Collins", 29, 9, 19, 986.33), - mall::floor::store::employee::Employee::new("Areeb Roberson", 54, 9, 22, 957.82), - mall::floor::store::employee::Employee::new("Rocco Amin", 44, 13, 23, 689.21), - ]; - let grill_emp = vec![ - mall::floor::store::employee::Employee::new("Rhian Crowther", 45, 9, 15, 841.18), - mall::floor::store::employee::Employee::new("Nikkita Steadman", 52, 14, 22, 858.61), - mall::floor::store::employee::Employee::new("Reginald Poole", 32, 9, 22, 1197.64), - mall::floor::store::employee::Employee::new("Minnie Bull", 54, 14, 22, 1229.73), - ]; - let sumo_emp = vec![ - mall::floor::store::employee::Employee::new("Chantelle Barajas", 20, 8, 22, 969.22), - mall::floor::store::employee::Employee::new("Hywel Rudd", 49, 12, 22, 695.74), - mall::floor::store::employee::Employee::new("Marianne Beasley", 55, 8, 14, 767.83), - ]; - let supermaket_emp = vec![ - mall::floor::store::employee::Employee::new("Amara Schaefer", 23, 9, 14, 796.21), - mall::floor::store::employee::Employee::new("Yara Wickens", 39, 9, 14, 853.42), - mall::floor::store::employee::Employee::new("Tomi Boyer", 64, 9, 14, 881.83), - mall::floor::store::employee::Employee::new("Greta Dickson", 42, 9, 14, 775.10), - mall::floor::store::employee::Employee::new("Caroline Finnegan", 41, 9, 14, 702.92), - mall::floor::store::employee::Employee::new("Indiana Baxter", 33, 13, 20, 991.71), - mall::floor::store::employee::Employee::new("Jadine Page", 48, 13, 20, 743.21), - mall::floor::store::employee::Employee::new("Husna Ryan", 43, 13, 20, 655.75), - mall::floor::store::employee::Employee::new("Tyler Hunt", 63, 13, 20, 668.25), - mall::floor::store::employee::Employee::new("Dahlia Caldwell", 56, 13, 20, 781.38), - mall::floor::store::employee::Employee::new("Chandler Mansell", 20, 19, 24, 656.75), - mall::floor::store::employee::Employee::new("Mohsin Mcgee", 30, 19, 24, 703.83), - mall::floor::store::employee::Employee::new("Antoine Goulding", 45, 19, 24, 697.12), - mall::floor::store::employee::Employee::new("Mark Barnard", 53, 19, 24, 788.81), - ]; - - let ground_stores = vec![ - mall::floor::store::Store::new("Footzo", 50, footzo_emp), - mall::floor::store::Store::new("Swashion", 43, swashion_emp), - ]; - let food_stores = vec![ - mall::floor::store::Store::new("PizBite", 60, pizbite_emp), - mall::floor::store::Store::new("Chillout Grill", 50, grill_emp), - mall::floor::store::Store::new("Sumo Food", 30, sumo_emp), - ]; - let supermarket = vec![mall::floor::store::Store::new( - "Pretail", - 950, - supermaket_emp, - )]; - - let floors = vec![ - mall::floor::Floor::new("Ground Floor", ground_stores, 300), - mall::floor::Floor::new("Food Floor", food_stores, 500), - mall::floor::Floor::new("Supermarket", supermarket, 1000), - ]; - - mall::Mall::new("La Vie Funchal", secs, floors) - } - - #[test] - fn biggest_store_tests() { - let mut shopping_mall = create_mall(); - - let mut tested = biggest_store(shopping_mall.clone()); - - assert_eq!("Pretail", tested.name); - assert_eq!(950, tested.square_meters); - - (&mut shopping_mall).floors[2].remove_store("Pretail".to_string()); - - tested = biggest_store(shopping_mall.clone()); - - assert_eq!("PizBite", tested.name); - assert_eq!(60, tested.square_meters); - } - - #[test] - fn highest_paid_test() { - let mut shopping_mall = create_mall(); - - let mut tested = highest_paid_employee(shopping_mall.clone()); - assert_eq!(1, tested.len()); - assert_eq!("Abdallah Stafford", tested[0].name); - assert_eq!(54, tested[0].age); - - let salary = shopping_mall.clone().floors[0].stores[0].employees[0].salary; - shopping_mall.floors[0].stores[0].employees[0].raise(tested[0].salary - salary); - tested = highest_paid_employee(shopping_mall.clone()); - - assert_eq!(2, tested.len()); - assert_eq!("Finbar Haines", tested[0].name); - assert_eq!(36, tested[0].age); - - assert_eq!(tested[1].salary, tested[0].salary); - } - - #[test] - fn nbr_of_employees_test() { - let mut shopping_mall = create_mall(); - - let mut tested = nbr_of_employees(shopping_mall.clone()); - assert_eq!(36, tested); - - shopping_mall.floors[2].stores[0].employees = vec![]; - - tested = nbr_of_employees(shopping_mall.clone()); - assert_eq!(22, tested); - } - - #[test] - fn check_for_securities_test() { - let mut shopping_mall = create_mall(); - - assert_eq!(3, shopping_mall.guards.len()); - - check_for_securities( - &mut shopping_mall, - vec![ - mall::guard::Guard::new("Peter Solomons", 45, 20), - mall::guard::Guard::new("William Charles", 32, 10), - mall::guard::Guard::new("Leonardo Changretta", 23, 0), - mall::guard::Guard::new("Vlad Levi", 38, 8), - mall::guard::Guard::new("Faruk Berkai", 40, 15), - mall::guard::Guard::new("Chritopher Smith", 35, 9), - mall::guard::Guard::new("Jason Mackie", 26, 2), - mall::guard::Guard::new("Kenzie Mair", 34, 8), - mall::guard::Guard::new("Bentley Larson", 33, 10), - mall::guard::Guard::new("Ray Storey", 37, 12), - ], - ); - - assert_eq!(9, shopping_mall.guards.len()); - } - - #[test] - fn cut_or_raise_test() { - let mut shopping_mall = create_mall(); - - cut_or_raise(&mut shopping_mall); - assert_eq!( - 585.792, - shopping_mall.floors[0].stores[0].employees[0].salary - ); - assert_eq!( - 1100.473, - shopping_mall.floors[0].stores[0].employees[2].salary - ); - - cut_or_raise(&mut shopping_mall); - assert_eq!( - 527.2128, - shopping_mall.floors[0].stores[0].employees[0].salary - ); - assert_eq!( - 1210.5203, - shopping_mall.floors[0].stores[0].employees[2].salary - ); - } -} diff --git a/tests/shopping_mall_test/src/mall.rs b/tests/shopping_mall_test/src/mall.rs new file mode 100644 index 00000000..50df5fc7 --- /dev/null +++ b/tests/shopping_mall_test/src/mall.rs @@ -0,0 +1,377 @@ +use shopping_mall::*; + +pub fn mall() -> Mall { + Mall::new( + "La Vie Funchal", + [ + ( + "John Oliver", + Guard { + age: 34, + years_experience: 7, + }, + ), + ( + "Logan West", + Guard { + age: 23, + years_experience: 2, + }, + ), + ( + "Bob Schumacher", + Guard { + age: 53, + years_experience: 15, + }, + ), + ] + .into(), + [ + ( + "Ground Floor", + Floor::new( + [ + ( + "Footzo", + Store::new( + [ + ( + "Finbar Haines", + Employee { + age: 36, + working_hours: (9, 14), + salary: 650.88, + }, + ), + ( + "Roksanna Rocha", + Employee { + age: 45, + working_hours: (13, 22), + salary: 772., + }, + ), + ( + "Sienna-Rose Penn", + Employee { + age: 26, + working_hours: (9, 22), + salary: 1000.43, + }, + ), + ] + .into(), + 50, + ), + ), + ( + "Swashion", + Store::new( + [ + ( + "Abdallah Stafford", + Employee { + age: 54, + working_hours: (8, 22), + salary: 1234.21, + }, + ), + ( + "Marian Snyder", + Employee { + age: 21, + working_hours: (8, 14), + salary: 831.9, + }, + ), + ( + "Amanda Mclean", + Employee { + age: 29, + working_hours: (13, 22), + salary: 1222.12, + }, + ), + ( + "Faizaan Castro", + Employee { + age: 32, + working_hours: (11, 18), + salary: 1106.43, + }, + ), + ] + .into(), + 43, + ), + ), + ] + .into(), + 300, + ), + ), + ( + "Food Floor", + Floor::new( + [ + ( + "PizBite", + Store::new( + [ + ( + "Juniper Cannon", + Employee { + age: 21, + working_hours: (16, 23), + salary: 804.35, + }, + ), + ( + "Alena Simon", + Employee { + age: 28, + working_hours: (9, 15), + salary: 973.54, + }, + ), + ( + "Yasemin Collins", + Employee { + age: 29, + working_hours: (9, 19), + salary: 986.33, + }, + ), + ( + "Areeb Roberson", + Employee { + age: 54, + working_hours: (9, 22), + salary: 957.82, + }, + ), + ( + "Rocco Amin", + Employee { + age: 44, + working_hours: (13, 23), + salary: 689.21, + }, + ), + ] + .into(), + 60, + ), + ), + ( + "Chillout Grill", + Store::new( + [ + ( + "Rhian Crowther", + Employee { + age: 45, + working_hours: (9, 15), + salary: 841.18, + }, + ), + ( + "Nikkita Steadman", + Employee { + age: 52, + working_hours: (14, 22), + salary: 858.61, + }, + ), + ( + "Reginald Poole", + Employee { + age: 32, + working_hours: (9, 22), + salary: 1197.64, + }, + ), + ( + "Minnie Bull", + Employee { + age: 54, + working_hours: (14, 22), + salary: 1229.73, + }, + ), + ] + .into(), + 50, + ), + ), + ( + "Sumo Food", + Store::new( + [ + ( + "Chantelle Barajas", + Employee { + age: 20, + working_hours: (8, 22), + salary: 969.22, + }, + ), + ( + "Hywel Rudd", + Employee { + age: 49, + working_hours: (12, 22), + salary: 695.74, + }, + ), + ( + "Marianne Beasley", + Employee { + age: 55, + working_hours: (8, 14), + salary: 767.83, + }, + ), + ] + .into(), + 30, + ), + ), + ] + .into(), + 500, + ), + ), + ( + "Supermarket", + Floor::new( + [( + "Pretail", + Store::new( + [ + ( + "Amara Schaefer", + Employee { + age: 23, + working_hours: (9, 14), + salary: 796.21, + }, + ), + ( + "Yara Wickens", + Employee { + age: 39, + working_hours: (9, 14), + salary: 853.42, + }, + ), + ( + "Tomi Boyer", + Employee { + age: 64, + working_hours: (9, 14), + salary: 881.83, + }, + ), + ( + "Greta Dickson", + Employee { + age: 42, + working_hours: (9, 14), + salary: 775.1, + }, + ), + ( + "Caroline Finnegan", + Employee { + age: 41, + working_hours: (9, 14), + salary: 702.92, + }, + ), + ( + "Indiana Baxter", + Employee { + age: 33, + working_hours: (13, 20), + salary: 991.71, + }, + ), + ( + "Jadine Page", + Employee { + age: 48, + working_hours: (13, 20), + salary: 743.21, + }, + ), + ( + "Husna Ryan", + Employee { + age: 43, + working_hours: (13, 20), + salary: 655.75, + }, + ), + ( + "Tyler Hunt", + Employee { + age: 63, + working_hours: (13, 20), + salary: 668.25, + }, + ), + ( + "Dahlia Caldwell", + Employee { + age: 56, + working_hours: (13, 20), + salary: 781.38, + }, + ), + ( + "Chandler Mansell", + Employee { + age: 20, + working_hours: (19, 24), + salary: 656.75, + }, + ), + ( + "Mohsin Mcgee", + Employee { + age: 30, + working_hours: (19, 24), + salary: 703.83, + }, + ), + ( + "Antoine Goulding", + Employee { + age: 45, + working_hours: (19, 24), + salary: 697.12, + }, + ), + ( + "Mark Barnard", + Employee { + age: 53, + working_hours: (19, 24), + salary: 788.81, + }, + ), + ] + .into(), + 950, + ), + )] + .into(), + 1000, + ), + ), + ] + .into(), + ) +} From 04b6636c1fb22e7841e6e12ffc5e8d61ae5da2d5 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 19 Mar 2025 12:17:40 +0000 Subject: [PATCH 18/26] fix(shopping_mall) --- solutions/shopping_mall/Cargo.toml | 1 + solutions/shopping_mall/src/lib.rs | 404 +++------------------------- solutions/shopping_mall/src/mall.rs | 239 +++++++--------- 3 files changed, 141 insertions(+), 503 deletions(-) diff --git a/solutions/shopping_mall/Cargo.toml b/solutions/shopping_mall/Cargo.toml index 9c2edfb5..cf41da71 100644 --- a/solutions/shopping_mall/Cargo.toml +++ b/solutions/shopping_mall/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +itertools = "0.14.0" diff --git a/solutions/shopping_mall/src/lib.rs b/solutions/shopping_mall/src/lib.rs index 79c85058..5ea0dde7 100644 --- a/solutions/shopping_mall/src/lib.rs +++ b/solutions/shopping_mall/src/lib.rs @@ -1,376 +1,56 @@ -/* -## shopping_mall - -### Instructions - -Using the `mall` module provided create the following **functions** to help run a shopping mall: - -- `biggest_store`: receives a `mall::Mall` and returns the `Store` with the biggest `square_meters`; -- `highest_paid_employees`: receives a `mall::Mall` and returns a vector containing the `Employee`(s) with the highest salaries; -- `nbr_of_employees`: receives a `mall::Mall` and returns the number of employees and guards, as a `usize`, in that mall; -- `fire_old_guards`: receives a `mall::Mall` and removes from the `mall::Mall.guards` all guards who are 50 years old or older; -- `check_for_guards`: receives a `mall::Mall` and a vector of `Guard` and, if there is not at least 1 guard for every 200 square meters of floor size, a guard should be added to the `mall::Mall.guards`; -- `cut_or_raise`: receives a `mall::Mall` and raises or cuts, the salary of every employee in the mall by 10%, if the employee works for more than 10 hours (consider that the guards are not employees from the mall). - - -### Example - -```rust -fn main() { - let secs = vec![ - mall::security::Guard::new("John Oliver", 34, 7), - mall::security::Guard::new("Logan West", 23, 2), - mall::security::Guard::new("Bob Schumacher", 53, 15), - ]; - - let footzo_emp = vec![ - mall::floor::store::employee::Employee::new("Finbar Haines", 36, 9, 14, 650.88), - mall::floor::store::employee::Employee::new("Roksanna Rocha", 45, 13, 22, 772.00), - mall::floor::store::employee::Employee::new("Sienna-Rose Penn", 26, 9, 22, 1000.43), - ]; - let swashion_emp = vec![ - mall::floor::store::employee::Employee::new("Abdallah Stafford", 54, 8, 22, 1234.21), - mall::floor::store::employee::Employee::new("Marian Snyder", 21, 8, 14, 831.90), - mall::floor::store::employee::Employee::new("Amanda Mclean", 29, 13, 22, 1222.12), - mall::floor::store::employee::Employee::new("Faizaan Castro", 32, 11, 18, 1106.43), - ]; - let pizbite_emp = vec![ - mall::floor::store::employee::Employee::new("Juniper Cannon", 21, 16, 23, 804.35), - mall::floor::store::employee::Employee::new("Alena Simon", 28, 9, 15, 973.54), - mall::floor::store::employee::Employee::new("Yasemin Collins", 29, 9, 19, 986.33), - mall::floor::store::employee::Employee::new("Areeb Roberson", 54, 9, 22, 957.82), - mall::floor::store::employee::Employee::new("Rocco Amin", 44, 13, 23, 689.21), - ]; - let grill_emp = vec![ - mall::floor::store::employee::Employee::new("Rhian Crowther", 45, 9, 15, 841.18), - mall::floor::store::employee::Employee::new("Nikkita Steadman", 52, 14, 22, 858.61), - mall::floor::store::employee::Employee::new("Reginald Poole", 32, 9, 22, 1197.64), - mall::floor::store::employee::Employee::new("Minnie Bull", 54, 14, 22, 1229.73), - ]; - let sumo_emp = vec![ - mall::floor::store::employee::Employee::new("Chantelle Barajas", 20, 8, 22, 969.22), - mall::floor::store::employee::Employee::new("Hywel Rudd", 49, 12, 22, 695.74), - mall::floor::store::employee::Employee::new("Marianne Beasley", 55, 8, 14, 767.83), - ]; - let supermaket_emp = vec![ - mall::floor::store::employee::Employee::new("Amara Schaefer", 23, 9, 14, 796.21), - mall::floor::store::employee::Employee::new("Yara Wickens", 39, 9, 14, 853.42), - mall::floor::store::employee::Employee::new("Tomi Boyer", 64, 9, 14, 881.83), - mall::floor::store::employee::Employee::new("Greta Dickson", 42, 9, 14, 775.10), - mall::floor::store::employee::Employee::new("Caroline Finnegan", 41, 9, 14, 702.92), - mall::floor::store::employee::Employee::new("Indiana Baxter", 33, 13, 20, 991.71), - mall::floor::store::employee::Employee::new("Jadine Page", 48, 13, 20, 743.21), - mall::floor::store::employee::Employee::new("Husna Ryan", 43, 13, 20, 655.75), - mall::floor::store::employee::Employee::new("Tyler Hunt", 63, 13, 20, 668.25), - mall::floor::store::employee::Employee::new("Dahlia Caldwell", 56, 13, 20, 781.38), - mall::floor::store::employee::Employee::new("Chandler Mansell", 20, 19, 24, 656.75), - mall::floor::store::employee::Employee::new("Mohsin Mcgee", 30, 19, 24, 703.83), - mall::floor::store::employee::Employee::new("Antoine Goulding", 45, 19, 24, 697.12), - mall::floor::store::employee::Employee::new("Mark Barnard", 53, 19, 24, 788.81), - ]; - - let ground_stores = vec![ - store::Store::new("Footzo", 50, footzo_emp), - store::Store::new("Swashion", 43, swashion_emp), - ]; - let food_stores = vec![ - store::Store::new("PizBite", 60, pizbite_emp), - store::Store::new("Chillout Grill", 50, grill_emp), - store::Store::new("Sumo Food", 30, sumo_emp), - ]; - let supermarket = vec![store::Store::new("Pretail", 950, supermaket_emp)]; - - let floors = vec![ - floor::Floor::new("Ground Floor", ground_stores, 300), - floor::Floor::new("Food Floor", food_stores, 500), - floor::Floor::new("Supermarket", supermarket, 1000), - ]; - - let mall_la_vie = mall::Mall::new("La Vie Funchal", secs, floors); - - println!("{:?}", &mall_la_vie); -} -``` - -*/ pub mod mall; -pub use floor::store; -pub use mall::floor; -pub use mall::guard; +use itertools::Itertools; pub use mall::*; -pub use store::employee; - -pub fn biggest_store(mall: mall::Mall) -> store::Store { - let mut res: store::Store = store::Store::new("", 0, vec![]); - - for floor_a in mall.floors.iter() { - for shop in floor_a.stores.iter() { - if shop.square_meters > res.square_meters { - res = shop.clone(); - } - } - } - - res +use std::collections::HashMap; + +pub fn biggest_store(mall: &Mall) -> (&String, &Store) { + mall.floors + .values() + .flat_map(|f| &f.stores) + .max_by_key(|(_, s)| s.square_meters) + .unwrap() } -pub fn highest_paid_employee(mall: mall::Mall) -> Vec { - let mut res = vec![employee::Employee::new("", 0, 0, 0, 0.0)]; - for elem in mall.floors.iter() { - for shop in elem.stores.iter() { - for emp in shop.employees.clone().into_iter() { - if emp.salary > res[0].salary { - res[0] = emp.clone(); - } else if emp.salary == res[0].salary { - res.push(emp.clone()); - } - } - } - } - res +pub fn highest_paid_employee(mall: &Mall) -> Vec<(&String, &Employee)> { + mall.floors + .values() + .flat_map(|f| f.stores.values().flat_map(|s| &s.employees)) + .max_set_by(|(_, a), (_, b)| a.salary.total_cmp(&b.salary)) } -pub fn nbr_of_employees(mall: mall::Mall) -> usize { - let mut res = 0; - - for floor_a in mall.floors.iter() { - for shop in floor_a.stores.iter() { - res += shop.employees.len(); - } - } - - res + mall.guards.len() +pub fn nbr_of_employees(mall: &Mall) -> usize { + mall.floors + .values() + .flat_map(|f| f.stores.values().flat_map(|s| &s.employees)) + .count() + + mall.guards.len() } -pub fn check_for_securities(mall: &mut mall::Mall, available_sec: Vec) { - let mut size = 0; - for floor in mall.floors.iter() { - size += floor.size_limit; - } +pub fn check_for_securities(mall: &mut Mall, available_sec: HashMap) { + let total_size = mall.floors.values().map(|f| f.size_limit).sum::(); - let mut i = 0; - while (mall.guards.len() as f64) < size as f64 / 200.0 { - mall.hire_guard(available_sec[i].clone()); - i += 1; - } -} + let total_areas = total_size / 200; + let unguarded_areas = total_areas as usize - mall.guards.len(); -pub fn cut_or_raise(mall: &mut mall::Mall) { - for (i, elem) in mall.clone().floors.iter().enumerate() { - for (j, shop) in elem.stores.iter().enumerate() { - for (z, emp) in shop.employees.iter().enumerate() { - if emp.working_hours.1 - emp.working_hours.0 >= 10 { - mall.floors[i].stores[j].employees[z].raise(emp.salary * 0.1); - } else { - mall.floors[i].stores[j].employees[z].cut(emp.salary * 0.1); - } - } - } - } + available_sec + .into_iter() + .take(unguarded_areas) + .for_each(|(name, guard)| { + mall.hire_guard(name, guard); + }); } -#[cfg(test)] -mod tests { - use super::*; - - fn create_mall() -> mall::Mall { - let secs = vec![ - mall::guard::Guard::new("John Oliver", 34, 7), - mall::guard::Guard::new("Logan West", 23, 2), - mall::guard::Guard::new("Bob Schumacher", 53, 15), - ]; - - let footzo_emp = vec![ - mall::floor::store::employee::Employee::new("Finbar Haines", 36, 9, 14, 650.88), - mall::floor::store::employee::Employee::new("Roksanna Rocha", 45, 13, 22, 772.00), - mall::floor::store::employee::Employee::new("Sienna-Rose Penn", 26, 9, 22, 1000.43), - ]; - let swashion_emp = vec![ - mall::floor::store::employee::Employee::new("Abdallah Stafford", 54, 8, 22, 1234.21), - mall::floor::store::employee::Employee::new("Marian Snyder", 21, 8, 14, 831.90), - mall::floor::store::employee::Employee::new("Amanda Mclean", 29, 13, 22, 1222.12), - mall::floor::store::employee::Employee::new("Faizaan Castro", 32, 11, 18, 1106.43), - ]; - let pizbite_emp = vec![ - mall::floor::store::employee::Employee::new("Juniper Cannon", 21, 16, 23, 804.35), - mall::floor::store::employee::Employee::new("Alena Simon", 28, 9, 15, 973.54), - mall::floor::store::employee::Employee::new("Yasemin Collins", 29, 9, 19, 986.33), - mall::floor::store::employee::Employee::new("Areeb Roberson", 54, 9, 22, 957.82), - mall::floor::store::employee::Employee::new("Rocco Amin", 44, 13, 23, 689.21), - ]; - let grill_emp = vec![ - mall::floor::store::employee::Employee::new("Rhian Crowther", 45, 9, 15, 841.18), - mall::floor::store::employee::Employee::new("Nikkita Steadman", 52, 14, 22, 858.61), - mall::floor::store::employee::Employee::new("Reginald Poole", 32, 9, 22, 1197.64), - mall::floor::store::employee::Employee::new("Minnie Bull", 54, 14, 22, 1229.73), - ]; - let sumo_emp = vec![ - mall::floor::store::employee::Employee::new("Chantelle Barajas", 20, 8, 22, 969.22), - mall::floor::store::employee::Employee::new("Hywel Rudd", 49, 12, 22, 695.74), - mall::floor::store::employee::Employee::new("Marianne Beasley", 55, 8, 14, 767.83), - ]; - let supermaket_emp = vec![ - mall::floor::store::employee::Employee::new("Amara Schaefer", 23, 9, 14, 796.21), - mall::floor::store::employee::Employee::new("Yara Wickens", 39, 9, 14, 853.42), - mall::floor::store::employee::Employee::new("Tomi Boyer", 64, 9, 14, 881.83), - mall::floor::store::employee::Employee::new("Greta Dickson", 42, 9, 14, 775.10), - mall::floor::store::employee::Employee::new("Caroline Finnegan", 41, 9, 14, 702.92), - mall::floor::store::employee::Employee::new("Indiana Baxter", 33, 13, 20, 991.71), - mall::floor::store::employee::Employee::new("Jadine Page", 48, 13, 20, 743.21), - mall::floor::store::employee::Employee::new("Husna Ryan", 43, 13, 20, 655.75), - mall::floor::store::employee::Employee::new("Tyler Hunt", 63, 13, 20, 668.25), - mall::floor::store::employee::Employee::new("Dahlia Caldwell", 56, 13, 20, 781.38), - mall::floor::store::employee::Employee::new("Chandler Mansell", 20, 19, 24, 656.75), - mall::floor::store::employee::Employee::new("Mohsin Mcgee", 30, 19, 24, 703.83), - mall::floor::store::employee::Employee::new("Antoine Goulding", 45, 19, 24, 697.12), - mall::floor::store::employee::Employee::new("Mark Barnard", 53, 19, 24, 788.81), - ]; - - let ground_stores = vec![ - store::Store::new("Footzo", 50, footzo_emp), - store::Store::new("Swashion", 43, swashion_emp), - ]; - let food_stores = vec![ - store::Store::new("PizBite", 60, pizbite_emp), - store::Store::new("Chillout Grill", 50, grill_emp), - store::Store::new("Sumo Food", 30, sumo_emp), - ]; - let supermarket = vec![store::Store::new("Pretail", 950, supermaket_emp)]; - - let floors = vec![ - floor::Floor::new("Ground Floor", ground_stores, 300), - floor::Floor::new("Food Floor", food_stores, 500), - floor::Floor::new("Supermarket", supermarket, 1000), - ]; - - mall::Mall::new("La Vie Funchal", secs, floors) - } - - #[test] - fn biggest_store_tests() { - let mut shopping_mall = create_mall(); - - let mut tested = biggest_store(shopping_mall.clone()); - - assert_eq!("Pretail", tested.name); - assert_eq!(950, tested.square_meters); - - (&mut shopping_mall).floors[2].remove_store("Pretail".to_string()); - - tested = biggest_store(shopping_mall.clone()); - - assert_eq!("PizBite", tested.name); - assert_eq!(60, tested.square_meters); - } - - #[test] - fn highest_paid_test() { - let mut shopping_mall = create_mall(); - - let mut tested = highest_paid_employee(shopping_mall.clone()); - assert_eq!(1, tested.len()); - assert_eq!("Abdallah Stafford", tested[0].name); - assert_eq!(54, tested[0].age); - - let salary = shopping_mall.clone().floors[0].stores[0].employees[0].salary; - shopping_mall.floors[0].stores[0].employees[0].raise(tested[0].salary - salary); - tested = highest_paid_employee(shopping_mall.clone()); - - assert_eq!(2, tested.len()); - assert_eq!("Finbar Haines", tested[0].name); - assert_eq!(36, tested[0].age); - - assert_eq!(tested[1].salary, tested[0].salary); - } - - #[test] - fn fire_old_sec_test() { - let mut shopping_mall = create_mall(); - - fire_old_securities(&mut shopping_mall); - assert_eq!(2, shopping_mall.guards.len()); - - shopping_mall.guards.append(&mut vec![ - mall::guard::Guard::new("Chris Esparza", 54, 12), - mall::guard::Guard::new("Kane Holloway", 53, 20), - mall::guard::Guard::new("Connor Wardle", 22, 1), - mall::guard::Guard::new("Louis Pickett", 26, 3), - mall::guard::Guard::new("Olly Middleton", 36, 9), - ]); - - assert_eq!(7, shopping_mall.guards.len()); - - fire_old_securities(&mut shopping_mall); - - assert_eq!(5, shopping_mall.guards.len()); - } - - #[test] - fn nbr_of_employees_test() { - let mut shopping_mall = create_mall(); - - let mut tested = nbr_of_employees(shopping_mall.clone()); - assert_eq!(36, tested); - - fire_old_securities(&mut shopping_mall); - - tested = nbr_of_employees(shopping_mall.clone()); - assert_eq!(35, tested); - - shopping_mall.floors[2].stores[0].employees = vec![]; - - tested = nbr_of_employees(shopping_mall.clone()); - assert_eq!(21, tested); - } - - #[test] - fn check_for_securities_test() { - let mut shopping_mall = create_mall(); - - assert_eq!(3, shopping_mall.guards.len()); - - check_for_securities( - &mut shopping_mall, - vec![ - guard::Guard::new("Peter Solomons", 45, 20), - guard::Guard::new("William Charles", 32, 10), - guard::Guard::new("Leonardo Changretta", 23, 0), - guard::Guard::new("Vlad Levi", 38, 8), - guard::Guard::new("Faruk Berkai", 40, 15), - guard::Guard::new("Chritopher Smith", 35, 9), - guard::Guard::new("Jason Mackie", 26, 2), - guard::Guard::new("Kenzie Mair", 34, 8), - guard::Guard::new("Bentley Larson", 33, 10), - guard::Guard::new("Ray Storey", 37, 12), - ], - ); - - assert_eq!(9, shopping_mall.guards.len()); - } - - #[test] - fn cut_or_raise_test() { - let mut shopping_mall = create_mall(); - - cut_or_raise(&mut shopping_mall); - assert_eq!( - 585.792, - shopping_mall.floors[0].stores[0].employees[0].salary - ); - assert_eq!( - 1100.473, - shopping_mall.floors[0].stores[0].employees[2].salary - ); - - cut_or_raise(&mut shopping_mall); - assert_eq!( - 527.2128, - shopping_mall.floors[0].stores[0].employees[0].salary - ); - assert_eq!( - 1210.5203, - shopping_mall.floors[0].stores[0].employees[2].salary - ); - } +pub fn cut_or_raise(mall: &mut Mall) { + mall.floors + .values_mut() + .flat_map(|f| f.stores.values_mut().flat_map(|s| s.employees.values_mut())) + .for_each(|e| { + let shift_hours = e.working_hours.1 - e.working_hours.0; + if shift_hours >= 10 { + e.raise(e.salary * 0.1); + } else { + e.cut(e.salary * 0.1); + } + }); } diff --git a/solutions/shopping_mall/src/mall.rs b/solutions/shopping_mall/src/mall.rs index dfd3035d..4391bcac 100644 --- a/solutions/shopping_mall/src/mall.rs +++ b/solutions/shopping_mall/src/mall.rs @@ -1,176 +1,133 @@ +use std::collections::HashMap; + +#[inline] +fn coerce_map(m: HashMap, V>) -> HashMap { + m.into_iter().map(|(k, v)| (k.into(), v)).collect() +} + #[derive(Debug, Clone, PartialEq)] pub struct Mall { pub name: String, - pub guards: Vec, - pub floors: Vec, + pub guards: HashMap, + pub floors: HashMap, } impl Mall { - #[allow(dead_code)] - pub fn new(name: &str, guards: Vec, floors: Vec) -> Mall { - Mall { - name: name.to_string(), - guards: guards, - floors: floors, + pub fn new( + name: impl Into, + guards: HashMap, Guard>, + floors: HashMap, Floor>, + ) -> Self { + Self { + name: name.into(), + guards: coerce_map(guards), + floors: coerce_map(floors), } } - #[allow(dead_code)] - pub fn change_name(&mut self, new_name: &str) { - self.name = new_name.to_string(); + pub fn change_name(&mut self, new_name: impl Into) { + self.name = new_name.into(); + } + + pub fn hire_guard(&mut self, name: impl Into, guard: Guard) { + self.guards.insert(name.into(), guard); } - #[allow(dead_code)] - pub fn hire_guard(&mut self, guard: guard::Guard) { - self.guards.push(guard); + pub fn fire_guard(&mut self, name: impl Into) { + self.guards.remove(&name.into()); } } -pub mod guard { +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Guard { + pub age: u32, + pub years_experience: u32, +} - #[derive(Debug, Clone, PartialEq)] - pub struct Guard { - pub name: String, - pub age: u8, - pub years_experience: u8, - } +#[derive(Debug, Clone, PartialEq)] +pub struct Floor { + pub stores: HashMap, + pub size_limit: u64, +} - impl Guard { - #[allow(dead_code)] - pub fn new(name: &str, age: u8, years_experience: u8) -> Guard { - Guard { - name: name.to_string(), - age: age, - years_experience: years_experience, - } +impl Floor { + pub fn new(stores: HashMap, Store>, size_limit: u64) -> Self { + Self { + stores: coerce_map(stores), + size_limit, } } -} -pub mod floor { - - #[derive(Debug, Clone, PartialEq)] - pub struct Floor { - pub name: String, - pub stores: Vec, - pub size_limit: u64, + pub fn replace_store(&mut self, store: impl Into, with: Store) { + self.stores.entry(store.into()).and_modify(|v| *v = with); } - impl Floor { - #[allow(dead_code)] - pub fn new(name: &str, stores: Vec, store_limit: u64) -> Floor { - Floor { - name: name.to_string(), - stores: stores, - size_limit: store_limit, - } - } + pub fn add_store(&mut self, name: impl Into, store: Store) -> Result<(), ()> { + let has_space = self.size_limit + >= self.stores.values().map(|s| s.square_meters).sum::() + store.square_meters; - #[allow(dead_code)] - pub fn change_store(&mut self, store: &str, new_store: store::Store) { - let pos = self.stores.iter().position(|x| x.name == store).unwrap(); - self.stores[pos] = new_store; + if has_space { + self.stores.insert(name.into(), store); + Ok(()) + } else { + Err(()) } + } - #[allow(dead_code)] - pub fn add_store(&mut self, new_store: store::Store) { - let mut current_floor_size = 0; + pub fn remove_store(&mut self, name: impl Into) { + self.stores.remove(&name.into()); + } +} - for store in self.stores.iter() { - current_floor_size += store.square_meters; - } +#[derive(Debug, Clone, PartialEq)] +pub struct Store { + pub employees: HashMap, + pub square_meters: u64, +} - if self.size_limit < current_floor_size + new_store.square_meters { - self.stores.push(new_store); - } +impl Store { + pub fn new(employees: HashMap, Employee>, square_meters: u64) -> Self { + Self { + employees: coerce_map(employees), + square_meters, } + } - #[allow(dead_code)] - pub fn remove_store(&mut self, store_name: String) { - self.stores.retain(|x| x.name != store_name); - } + pub fn hire_employee(&mut self, name: impl Into, employee: Employee) { + self.employees.insert(name.into(), employee); } - pub mod store { + pub fn fire_employee(&mut self, name: impl Into) { + self.employees.remove(&name.into()); + } - #[derive(Debug, Clone, PartialEq)] - pub struct Store { - pub name: String, - pub square_meters: u64, - pub employees: Vec, - } + pub fn expand(&mut self, by: u64) { + self.square_meters += by; + } +} - impl Store { - #[allow(dead_code)] - pub fn new(name: &str, space: u64, employees: Vec) -> Store { - Store { - name: name.to_string(), - square_meters: space, - employees: employees, - } - } - - #[allow(dead_code)] - pub fn hire_employee(&mut self, employee: employee::Employee) { - self.employees.push(employee); - } - #[allow(dead_code)] - pub fn fire_employee(&mut self, employee_name: &str) { - self.employees.retain(|x| x.name != employee_name); - } - #[allow(dead_code)] - pub fn expand(&mut self, square_meters: u64) { - self.square_meters += square_meters; - } - } +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Employee { + pub age: u32, + // The employee works from `working_hours.0` to `working_hours.1` + pub working_hours: (u32, u32), + pub salary: f64, +} - pub mod employee { - - #[derive(Debug, Clone, PartialEq)] - pub struct Employee { - pub name: String, - pub age: u8, - pub working_hours: (u8, u8), - pub salary: f64, - } - - impl Employee { - #[allow(dead_code)] - pub fn new( - name: &str, - age: u8, - entry_hour: u8, - exit_hour: u8, - salary: f64, - ) -> Employee { - Employee { - name: name.to_string(), - age: age, - working_hours: (entry_hour, exit_hour), - salary: salary, - } - } - - #[allow(dead_code)] - pub fn birthday(&mut self) { - self.age += 1; - } - - #[allow(dead_code)] - pub fn change_workload(&mut self, entry_hour: u8, exit_hour: u8) { - self.working_hours = (entry_hour, exit_hour); - } - - #[allow(dead_code)] - pub fn raise(&mut self, amount: f64) { - self.salary += amount; - } - - #[allow(dead_code)] - pub fn cut(&mut self, amount: f64) { - self.salary = self.salary - amount; - } - } - } +impl Employee { + pub fn birthday(&mut self) { + self.age += 1; + } + + pub fn change_workload(&mut self, from: u32, to: u32) { + self.working_hours = (from, to); + } + + pub fn raise(&mut self, amount: f64) { + self.salary += amount; + } + + pub fn cut(&mut self, amount: f64) { + self.salary -= amount; } } From a29e4c8600901dd8ea862f0b055cc84c54b401f9 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 19 Mar 2025 14:27:04 +0000 Subject: [PATCH 19/26] fix(expected_variable) --- tests/expected_variable_test/src/lib.rs | 182 +++++++++--------------- 1 file changed, 66 insertions(+), 116 deletions(-) diff --git a/tests/expected_variable_test/src/lib.rs b/tests/expected_variable_test/src/lib.rs index 0d9e3942..e0b14c9a 100644 --- a/tests/expected_variable_test/src/lib.rs +++ b/tests/expected_variable_test/src/lib.rs @@ -1,122 +1,72 @@ -/* -## expected_variable - -### Instructions - -Create a **function** named `expected_variable` that receives a string to compare and an expected string. It should return an `Option`. Every comparison should be case `insensitive`. - -If the compared string is **not** in camel case or snake case, `expected_variable` returns `None`. You can use the `case` crate to figure that out. Otherwise, the compared string should be compared to the expected string using the `edit_distance` **function** that you have already created. - -If the result of `edit_distance` has more than 50% alikeness to the expected string, considering the length of the expected string and the result of `edit_distance`, the function should return that value with a `'%'` symbol after the number. -Otherwise `expected_value` should return `None`. - -Example: - -```rs -fn main() { - println!( - "{} close to it", - expected_variable("On_Point", "on_point").unwrap() - ); // -> 100% - println!( - "{} close to it", - expected_variable("soClose", "So_Close").unwrap() - ); // -> 88% - println!( - "{} close to it", - expected_variable("something", "something_completely_different").unwrap() - ); // -> Fail - println!( - "{} close to it", - expected_variable("BenedictCumberbatch", "BeneficialCucumbersnatch").unwrap() - ); // -> 73% +use expected_variable::*; + +#[test] +fn regular_cases() { + assert_eq!( + Some("88%".to_owned()), + expected_variable("soClose", "so_close") + ); + assert_eq!( + Some("73%".to_owned()), + expected_variable("lets_try", "lets_try_it") + ); + assert_eq!( + Some("64%".to_owned()), + expected_variable("GoodJob", "VeryGoodJob") + ); + assert_eq!( + Some("67%".to_owned()), + expected_variable("BenedictCumberbatch", "BeneficialCucumbersnatch") + ); } -``` - -*/ - -#[cfg(test)] -mod tests { - use expected_variable::*; - - #[test] - fn no_variable_case() { - let mut result = expected_variable("It is simply not a variable case", "gonnaFail"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("do-not-use-dashes", "do-not-use-dashes"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("Not a variable case", "needs to fail"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("This should be None", "needs to fail"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("Do not use spaces", "Do not use spaces"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - } - - #[test] - fn incorrect_names() { - let mut result = expected_variable("it_is_done", "almost_there"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); +#[test] +fn none_cases() { + assert_eq!( + None, + expected_variable("It is simply not a variable case", "gonnaFail") + ); + + assert_eq!( + None, + expected_variable("do-not-use-dashes", "do-not-use-dashes") + ); + + assert_eq!( + None, + expected_variable("Not a variable case", "needs to fail") + ); + + assert_eq!( + None, + expected_variable("This should be None", "needs to fail") + ); + + assert_eq!( + None, + expected_variable("Do not use spaces", "Do not use spaces") + ); +} - result = expected_variable("frankenstein", "Dracula"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - } +#[test] +fn incorrect_cases() { + assert_eq!(None, expected_variable("it_is_done", "almost_there")); - #[test] - fn one_hundred_percent() { - assert_eq!( - "100%", - expected_variable("great_variable", "great_variable").unwrap() - ); - assert_eq!("100%", expected_variable("SpOtON", "SpotOn").unwrap()); - assert_eq!( - "100%", - expected_variable("Another_great_variable", "Another_great_variable").unwrap() - ); - } + assert_eq!(None, expected_variable("frankenstein", "Dracula")); +} - #[test] - fn passing_tests() { - assert_eq!("88%", expected_variable("soClose", "so_close").unwrap()); - assert_eq!("73%", expected_variable("lets_try", "lets_try_it").unwrap()); - assert_eq!("64%", expected_variable("GoodJob", "VeryGoodJob").unwrap()); - assert_eq!("64%", expected_variable("GoodJob", "VeryGoodJob").unwrap()); - assert_eq!( - "67%", - expected_variable("BenedictCumberbatch", "BeneficialCucumbersnatch").unwrap() - ); - } +#[test] +fn equal_cases() { + assert_eq!( + Some("100%".to_owned()), + expected_variable("great_variable", "great_variable") + ); + assert_eq!( + Some("100%".to_owned()), + expected_variable("SpOtON", "SpotOn") + ); + assert_eq!( + Some("100%".to_owned()), + expected_variable("Another_great_variable", "Another_great_variAble") + ); } From 98c5bc0bfaba2176e003860215de15e89fc28f37 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 19 Mar 2025 14:27:10 +0000 Subject: [PATCH 20/26] fix(expected_variable) --- solutions/expected_variable/Cargo.toml | 2 +- solutions/expected_variable/src/lib.rs | 153 ++----------------------- 2 files changed, 12 insertions(+), 143 deletions(-) diff --git a/solutions/expected_variable/Cargo.toml b/solutions/expected_variable/Cargo.toml index a7557a9d..b03727b1 100644 --- a/solutions/expected_variable/Cargo.toml +++ b/solutions/expected_variable/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" [dependencies] edit_distance = { path = "../edit_distance", version = "0.1.0" } -case = "1.0.0" \ No newline at end of file +convert_case = "0.6.0" diff --git a/solutions/expected_variable/src/lib.rs b/solutions/expected_variable/src/lib.rs index 001d9b67..e7eb1726 100644 --- a/solutions/expected_variable/src/lib.rs +++ b/solutions/expected_variable/src/lib.rs @@ -1,151 +1,20 @@ -/* -## expected_variable - -### Instructions - -Create a **function** named `expected_variable` that receives a string to compare and an expected string. It should return an `Option`. Every comparison should be case `insensitive`. - -If the compared string is **not** in camel case or snake case, `expected_variable` returns `None`. You can use the `case` crate to figure that out. Otherwise, the compared string should be compared to the expected string using the `edit_distance` **function** that you have already created. - -If the result of `edit_distance` has more than 50% alikeness to the expected string, considering the length of the expected string and the result of `edit_distance`, the function should return that value with a `'%'` symbol after the number. -Otherwise `expected_value` should return `None`. - -Example: - -```rs -fn main() { - println!( - "{} close to it", - expected_variable("On_Point", "on_point").unwrap() - ); // -> 100% - println!( - "{} close to it", - expected_variable("soClose", "So_Close").unwrap() - ); // -> 88% - println!( - "{:?}", - expected_variable("something", "something_completely_different") - ); // -> None - println!( - "{} close to it", - expected_variable("BenedictCumberbatch", "BeneficialCucumbersnatch").unwrap() - ); // -> 73% -} -``` - -*/ - -use case::CaseExt; +use convert_case::{Case, Casing}; use edit_distance::edit_distance; pub fn expected_variable(compare: &str, expected: &str) -> Option { - let compare_aux = compare.to_lowercase(); - let expected_aux = expected.to_lowercase(); - - if (compare_aux.to_ascii_lowercase() == compare_aux - || compare_aux.to_camel_lowercase() == compare_aux) - && !compare_aux.contains("-") - && !compare_aux.contains(" ") - { - let distance = edit_distance(&compare_aux, &expected_aux) as i64; + let compare = compare.to_lowercase(); + let expected = expected.to_lowercase(); - if distance == 0 { - return Some("100%".to_string()); - } - - let percentage = 100 - (distance * 100 / expected.len() as i64); - if percentage >= 50 { - let mut res = percentage.to_string(); - res.push_str("%"); - return Some(res); - } + if !compare.is_case(Case::Camel) && !compare.is_case(Case::Snake) { + return None; } - None -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn no_variable_case() { - let mut result = expected_variable("It is simply not a variable case", "gonnaFail"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("do-not-use-dashes", "do-not-use-dashes"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("Not a variable case", "needs to fail"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("This should be None", "needs to fail"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("Do not use spaces", "Do not use spaces"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - } - - #[test] - fn incorrect_names() { - let mut result = expected_variable("it_is_done", "almost_there"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - - result = expected_variable("frankenstein", "Dracula"); - assert!( - result.is_none(), - "Should have been None and not, {:?}", - result - ); - } - - #[test] - fn one_hundred_percent() { - assert_eq!( - "100%", - expected_variable("great_variable", "great_variable").unwrap() - ); - assert_eq!("100%", expected_variable("SpOtON", "SpotOn").unwrap()); - assert_eq!( - "100%", - expected_variable("Another_great_variable", "Another_great_variable").unwrap() - ); - } + let distance = edit_distance(&compare, &expected); - #[test] - fn passing_tests() { - assert_eq!("88%", expected_variable("soClose", "so_close").unwrap()); - assert_eq!("73%", expected_variable("lets_try", "lets_try_it").unwrap()); - assert_eq!("64%", expected_variable("GoodJob", "VeryGoodJob").unwrap()); - assert_eq!("64%", expected_variable("GoodJob", "VeryGoodJob").unwrap()); - assert_eq!( - "67%", - expected_variable("BenedictCumberbatch", "BeneficialCucumbersnatch").unwrap() - ); + let percentage = 100 - (distance * 100 / expected.len()).min(100); + if percentage >= 50 { + Some(format!("{percentage}%")) + } else { + None } } From 376e32cf710bc2d599d8c56ccbd007ffef3aeff7 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 19 Mar 2025 15:50:01 +0000 Subject: [PATCH 21/26] fix(mobs) --- solutions/mobs/src/{mobs => }/boss.rs | 5 +- solutions/mobs/src/lib.rs | 142 +++++++++++++++----------- solutions/mobs/src/member.rs | 25 +++++ solutions/mobs/src/mobs.rs | 79 -------------- solutions/mobs/src/mobs/member.rs | 33 ------ 5 files changed, 109 insertions(+), 175 deletions(-) rename solutions/mobs/src/{mobs => }/boss.rs (69%) create mode 100644 solutions/mobs/src/member.rs delete mode 100644 solutions/mobs/src/mobs.rs delete mode 100644 solutions/mobs/src/mobs/member.rs diff --git a/solutions/mobs/src/mobs/boss.rs b/solutions/mobs/src/boss.rs similarity index 69% rename from solutions/mobs/src/mobs/boss.rs rename to solutions/mobs/src/boss.rs index e5066289..484e7eb1 100644 --- a/solutions/mobs/src/mobs/boss.rs +++ b/solutions/mobs/src/boss.rs @@ -1,11 +1,12 @@ #[derive(Debug, Clone, PartialEq)] pub struct Boss { pub name: String, - pub age: u8, + pub age: u32, } impl Boss { - pub fn new(name: &str, age: u8) -> Self { + #[inline] + pub fn new(name: &str, age: u32) -> Self { Self { name: name.to_string(), age, diff --git a/solutions/mobs/src/lib.rs b/solutions/mobs/src/lib.rs index 76e34db8..a452aafd 100644 --- a/solutions/mobs/src/lib.rs +++ b/solutions/mobs/src/lib.rs @@ -1,61 +1,81 @@ -/* -## mobs - -### Instructions - -Create a different file and a folder called `mob.rs` and `mob` respectively. This will be the `mob` module that will contain: - -- a `Mob` structure that consists of: - - a String `name` - - a Boss struct `boss` - - a vector of Members `members` - - a vector of tuples containing a name String and a value u8 `cities` - - a u32 `wealth` - -The Mob struct should implement the following functions: - - - `recruit`, that adds a member to the members vector of the Mob when receiving a `member_name` (&str) and a `member_age` (u8) (the role should be Associate) - - `attack`, that receives another Mob and will remove the last member from the vector of Members from the Mob with the less power combat (in case of draw, the loser is the attacker). The power combat is calculated by its members: - - an Underboss power combat is 4 - - a Caporegime power combat is 3 - - a Soldier power combat is 2 - - an Associate power combat is 1 - - In case of one of the mobs stays without members, the winner mob adds to its cities every city of the loser mob and the same happens to the wealth, and the loser mob loses all cities and all wealth - - `steal`, that receives the targeted mob (Mob) and a value (u32) and adds to the own mob a value and subtracts the value - - `conquer_city`, that receives a vector of mobs, a city name (String) and a value (u8) and adds it to the vector of cities of the own mob, only if no mob in the vector owns a city with that name - -Also create two submodules of mob: - -- `boss` submodule which should contain: - - a `Boss` struct that consists of: - - a String `name` - - an u8 `age` - - a function `new` that returns a Boss on receiving a name (&str) and an age (u8) -- `member` submodule which consists of: - - a enum `Role` with the variants: - - Underboss - - Caporegime - - Soldier - - Associate - - a `Member` struct that consists of: - - a String name - - a enum Role `role` - - a u8 `age` - - the Member struct should implement a function `get_promotion` which will change the member role. If a member is an Associate, it will get promoted to Soldier; a Soldier is promoted to a Caporegime and a Caporegime is promoted to Underboss - - a function `new` that return a Member on receiving a name(&str), a role (Role) and an age (u8) - - You must include `#[derive(Debug, CLone, PartialEq)]` on top of every struct and the enum. - - Create also a `lib.rs` file containing the following code, in order to test your code: - -```rust -mod mob; - -pub use mob::*; -``` - - */ - -mod mobs; - -pub use mobs::*; +pub mod boss; +pub mod member; + +use std::collections::{HashMap, HashSet}; + +pub use boss::*; +pub use member::*; + +#[derive(PartialEq, Debug, Clone)] +pub struct Mob { + pub name: String, + pub boss: Boss, + pub members: HashMap, + pub cities: HashSet, + pub wealth: u64, +} + +impl Mob { + #[inline] + pub fn recruit(&mut self, (name, age): (&str, u32)) { + self.members.insert( + name.to_owned(), + Member { + role: Role::Associate, + age, + }, + ); + } + + #[inline] + fn calculate_power(&self) -> usize { + self.members + .values() + .map(|m| match m.role { + Role::Underboss => 4, + Role::Caporegime => 3, + Role::Soldier => 2, + Role::Associate => 1, + }) + .sum() + } + + #[inline] + fn give_cities(&mut self, to: &mut Mob) { + to.cities.extend(self.cities.drain()) + } + + pub fn attack(&mut self, target: &mut Mob) { + let (winner, loser) = if self.calculate_power() > target.calculate_power() { + (self, target) + } else { + (target, self) + }; + + let youngest_age = loser.members.values().map(|m| m.age).min().unwrap(); + + loser.members.retain(|_, m| m.age > youngest_age); + + if loser.members.is_empty() { + loser.give_cities(winner); + winner.wealth += loser.wealth; + loser.wealth = 0; + } + } + + pub fn steal(&mut self, target: &mut Mob, value: u64) { + let clamped = value.min(target.wealth); + self.wealth += clamped; + target.wealth -= clamped; + } + + pub fn conquer_city(&mut self, mobs: &[&Mob], wanted_city: String) { + if !mobs + .iter() + .flat_map(|m| &m.cities) + .any(|c| *c == wanted_city) + { + self.cities.insert(wanted_city); + } + } +} diff --git a/solutions/mobs/src/member.rs b/solutions/mobs/src/member.rs new file mode 100644 index 00000000..74d7c127 --- /dev/null +++ b/solutions/mobs/src/member.rs @@ -0,0 +1,25 @@ +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Role { + Underboss, + Soldier, + Caporegime, + Associate, +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct Member { + pub role: Role, + pub age: u32, +} + +impl Member { + #[inline] + pub fn get_promotion(&mut self) { + self.role = match self.role { + Role::Associate => Role::Soldier, + Role::Soldier => Role::Caporegime, + Role::Caporegime => Role::Underboss, + _ => unreachable!(), + }; + } +} diff --git a/solutions/mobs/src/mobs.rs b/solutions/mobs/src/mobs.rs deleted file mode 100644 index 9fd0d262..00000000 --- a/solutions/mobs/src/mobs.rs +++ /dev/null @@ -1,79 +0,0 @@ -pub mod boss; -pub mod member; - -use boss::Boss; -use member::{Member, Role}; - -#[derive(Debug, Clone)] -pub struct Mob { - pub name: String, - pub boss: Boss, - pub members: Vec, - pub cities: Vec<(String, u8)>, - pub wealth: u32, -} - -impl Mob { - pub fn recruit(&mut self, member_name: &str, member_age: u8) { - self.members - .push(Member::new(member_name, Role::Associate, member_age)); - } - - pub fn attack(&mut self, target: &mut Mob) { - if calculate_power(self) > calculate_power(target) { - target.members.pop(); - } else { - self.members.pop(); - } - - if self.members.is_empty() { - switch_cities(target, self); - target.wealth += self.wealth; - self.cities = vec![]; - self.wealth = 0; - } else if target.members.is_empty() { - switch_cities(self, target); - self.wealth += target.wealth; - target.cities = vec![]; - target.wealth = 0; - } - } - - pub fn steal(&mut self, target: &mut Mob, value: u32) { - if target.wealth >= value { - self.wealth += value; - target.wealth -= value; - } else { - self.wealth += target.wealth; - target.wealth = 0; - } - } - - pub fn conquer_city(&mut self, mobs: Vec, wanted_city: String, value: u8) { - if !mobs - .into_iter() - .any(|ele| ele.cities.iter().any(|(city, _)| city == &wanted_city)) - { - self.cities.push((wanted_city, value)); - } - } -} - -fn calculate_power(mob: &Mob) -> usize { - let mut result = 0; - for member in &mob.members { - match member.role { - Role::Underboss => result += 4, - Role::Caporegime => result += 3, - Role::Soldier => result += 2, - Role::Associate => result += 1, - } - } - result -} - -fn switch_cities(winner: &mut Mob, loser: &Mob) { - for city in &loser.cities { - winner.cities.push(city.clone()); - } -} diff --git a/solutions/mobs/src/mobs/member.rs b/solutions/mobs/src/mobs/member.rs deleted file mode 100644 index ac485a69..00000000 --- a/solutions/mobs/src/mobs/member.rs +++ /dev/null @@ -1,33 +0,0 @@ -#[derive(Debug, Clone, PartialEq)] -pub enum Role { - Underboss, - Soldier, - Caporegime, - Associate, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Member { - pub name: String, - pub role: Role, - pub age: u8, -} - -impl Member { - pub fn get_promotion(&mut self) { - match self.role { - Role::Associate => self.role = Role::Soldier, - Role::Soldier => self.role = Role::Caporegime, - Role::Caporegime => self.role = Role::Underboss, - _ => {} - } - } - - pub fn new(name: &str, role: Role, age: u8) -> Self { - Self { - name: name.to_string(), - role, - age, - } - } -} From 8a51ba4a95241f88608a618eda5a6489260363e9 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 19 Mar 2025 15:50:08 +0000 Subject: [PATCH 22/26] fix(mobs) --- tests/mobs_test/src/lib.rs | 184 ++++++++++++++++++++++++++++++++++++ tests/mobs_test/src/main.rs | 170 --------------------------------- 2 files changed, 184 insertions(+), 170 deletions(-) create mode 100644 tests/mobs_test/src/lib.rs delete mode 100644 tests/mobs_test/src/main.rs diff --git a/tests/mobs_test/src/lib.rs b/tests/mobs_test/src/lib.rs new file mode 100644 index 00000000..e54ca35f --- /dev/null +++ b/tests/mobs_test/src/lib.rs @@ -0,0 +1,184 @@ +use mobs::*; + +#[test] +fn create_boss() { + assert_eq!( + Boss::new("Scarface", 43), + Boss { + name: "Scarface".to_owned(), + age: 43 + } + ); +} + +#[inline] +fn mobs() -> (Mob, Mob) { + ( + Mob { + name: "Hairy Giants".to_owned(), + boss: Boss::new("Louie HaHa", 36), + cities: ["San Francisco"].map(str::to_owned).into(), + members: [ + ( + "Benny Eggs", + Member { + role: Role::Soldier, + age: 28, + }, + ), + ( + "Jhonny", + Member { + role: Role::Associate, + age: 17, + }, + ), + ( + "Greasy Thumb", + Member { + role: Role::Soldier, + age: 30, + }, + ), + ( + "No Finger", + Member { + role: Role::Caporegime, + age: 32, + }, + ), + ] + .map(|(n, d)| (n.to_owned(), d)) + .into(), + wealth: 100000, + }, + Mob { + name: "Red Thorns".to_owned(), + boss: Boss::new("Big Tuna", 30), + cities: ["San Jose"].map(str::to_owned).into(), + members: [ + ( + "Knuckles", + Member { + role: Role::Soldier, + age: 25, + }, + ), + ( + "Baldy Dom", + Member { + role: Role::Caporegime, + age: 36, + }, + ), + ( + "Crazy Joe", + Member { + role: Role::Underboss, + age: 23, + }, + ), + ] + .map(|(n, d)| (n.to_owned(), d)) + .into(), + wealth: 70000, + }, + ) +} + +#[test] +fn mob_recruit() { + let (mut mob, _) = mobs(); + mob.recruit(("Rusty", 37)); + + assert_eq!( + mob.members.get("Rusty"), + Some(&Member { + role: Role::Associate, + age: 37, + }) + ); +} + +#[test] +fn member_get_promotion() { + let (mut mob, _) = mobs(); + + let member = mob.members.get_mut("Benny Eggs").unwrap(); + + member.get_promotion(); + assert_eq!(member.role, member::Role::Caporegime); + member.get_promotion(); + assert_eq!(member.role, member::Role::Underboss); +} + +#[test] +#[should_panic] +fn member_get_promotion_panic() { + let (_, mut mob) = mobs(); + + let member = mob.members.get_mut("Crazy Joe").unwrap(); + + member.get_promotion(); +} + +#[test] +fn mob_steal() { + let (mut a, mut b) = mobs(); + b.steal(&mut a, 10000); + assert_eq!(a.wealth, 90000); + assert_eq!(b.wealth, 80000); + + b.steal(&mut a, 1000000); + assert_eq!(a.wealth, 0); + assert_eq!(b.wealth, 170000); +} + +#[test] +fn mob_attack() { + let (mut a, mut b) = mobs(); + a.attack(&mut b); + + assert_eq!(a.members.len(), 3); + assert_eq!(b.members.len(), 3); +} + +#[test] +fn same_combat_power() { + let (mut a, mut b) = mobs(); + + a.recruit(("Stitches", 28)); + a.attack(&mut b); + + assert_eq!(a.members.len(), 4); + assert_eq!(b.members.len(), 3); +} + +#[test] +fn no_members_mob() { + let (mut a, mut b) = mobs(); + b.attack(&mut a); + a.attack(&mut b); + b.attack(&mut a); + b.attack(&mut a); + + assert_eq!(a.members.len(), 0); + assert_eq!(a.cities.len(), 0); + assert_eq!(a.wealth, 0); + + assert!(b + .cities + .is_superset(&["San Jose", "San Francisco"].map(str::to_owned).into())); + assert_eq!(b.wealth, 170000); +} + +#[test] +fn mob_conquer_city() { + let (mut a, mut b) = mobs(); + + b.conquer_city(&[&a], "Las Vegas".to_owned()); + assert!(b.cities.contains("Las Vegas")); + + a.conquer_city(&[&b], "Las Vegas".to_owned()); + assert_eq!(a.cities.len(), 1); +} diff --git a/tests/mobs_test/src/main.rs b/tests/mobs_test/src/main.rs deleted file mode 100644 index bb5924d7..00000000 --- a/tests/mobs_test/src/main.rs +++ /dev/null @@ -1,170 +0,0 @@ -use mobs::*; - -fn main() { - let (mafia1, mafia2) = ( - Mob { - name: "Hairy Giants".to_string(), - boss: boss::Boss::new("Louie HaHa", 36), - cities: vec![("San Francisco".to_string(), 7)], - members: vec![ - member::Member::new("Benny Eggs", member::Role::Soldier, 28), - member::Member::new("Jhonny", member::Role::Associate, 17), - member::Member::new("Greasy Thumb", member::Role::Soldier, 30), - member::Member::new("No Finger", member::Role::Caporegime, 32), - ], - wealth: 100000, - }, - Mob { - name: "Red Thorns".to_string(), - boss: boss::Boss::new("Big Tuna", 30), - cities: vec![("San Jose".to_string(), 5)], - members: vec![ - member::Member::new("Knuckles", member::Role::Soldier, 25), - member::Member::new("Baldy Dom", member::Role::Caporegime, 36), - member::Member::new("Crazy Joe", member::Role::Underboss, 23), - ], - wealth: 70000, - }, - ); - - println!("{:?}\n{:?}", mafia1, mafia2); -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn create_boss_and_members() { - assert_eq!( - boss::Boss::new("Scarface", 43), - boss::Boss { - name: "Scarface".to_string(), - age: 43 - } - ); - assert_eq!( - member::Member::new("Crazy Joe", member::Role::Soldier, 27), - member::Member { - name: "Crazy Joe".to_string(), - role: member::Role::Soldier, - age: 27 - } - ); - assert_eq!( - member::Member::new("Louie HaHa", member::Role::Caporegime, 30), - member::Member { - name: "Louie HaHa".to_string(), - role: member::Role::Caporegime, - age: 30 - } - ); - } - - fn create_mobs() -> (Mob, Mob) { - ( - Mob { - name: "Hairy Giants".to_string(), - boss: boss::Boss::new("Louie HaHa", 36), - cities: vec![("San Francisco".to_string(), 7)], - members: vec![ - member::Member::new("Benny Eggs", member::Role::Soldier, 28), - member::Member::new("Jhonny", member::Role::Associate, 17), - member::Member::new("Greasy Thumb", member::Role::Soldier, 30), - member::Member::new("No Finger", member::Role::Caporegime, 32), - ], - wealth: 100000, - }, - Mob { - name: "Red Thorns".to_string(), - boss: boss::Boss::new("Big Tuna", 30), - cities: vec![("San Jose".to_string(), 5)], - members: vec![ - member::Member::new("Knuckles", member::Role::Soldier, 25), - member::Member::new("Baldy Dom", member::Role::Caporegime, 36), - member::Member::new("Crazy Joe", member::Role::Underboss, 23), - ], - wealth: 70000, - }, - ) - } - - #[test] - fn mob_recruit() { - let (mut a, _b) = create_mobs(); - a.recruit("Rusty", 37); - - assert_eq!( - a.members[a.members.len() - 1], - member::Member::new("Rusty", member::Role::Associate, 37) - ); - } - - #[test] - fn mob_steal() { - let (mut a, mut b) = create_mobs(); - b.steal(&mut a, 10000); - assert_eq!(a.wealth, 90000); - assert_eq!(b.wealth, 80000); - - b.steal(&mut a, 100000); - assert_eq!(a.wealth, 0); - assert_eq!(b.wealth, 170000); - } - #[test] - fn mob_attack() { - let (mut a, mut b) = create_mobs(); - a.attack(&mut b); - - assert_eq!(a.members.len(), 3); - assert_eq!(b.members.len(), 3); - } - - #[test] - fn member_get_promotion() { - let (mut a, _) = create_mobs(); - a.members[0].get_promotion(); - assert_eq!(a.members[0].role, member::Role::Caporegime); - a.members[0].get_promotion(); - assert_eq!(a.members[0].role, member::Role::Underboss); - } - - #[test] - fn same_combat_power() { - let (mut a, mut b) = create_mobs(); - - a.recruit("Stitches", 28); - a.attack(&mut b); - - assert_eq!(a.members.len(), 4); - assert_eq!(b.members.len(), 3); - } - - #[test] - fn no_members_mob() { - let (mut a, mut b) = create_mobs(); - b.attack(&mut a); - a.attack(&mut b); - b.attack(&mut a); - b.attack(&mut a); - - assert_eq!(a.members.len(), 0); - assert_eq!(a.cities.len(), 0); - assert_eq!(a.wealth, 0); - - assert_eq!(b.cities[0], ("San Jose".to_string(), 5)); - assert_eq!(b.cities[1], ("San Francisco".to_string(), 7)); - assert_eq!(b.wealth, 170000); - } - - #[test] - fn mob_conquer_city() { - let (mut a, mut b) = create_mobs(); - - b.conquer_city(vec![a.clone()], "Las Vegas".to_string(), 9); - assert_eq!(b.cities[1], ("Las Vegas".to_string(), 9)); - - a.conquer_city(vec![b.clone()], "Las Vegas".to_string(), 6); - assert_eq!(a.cities.len(), 1); - } -} From dad8bce0b3a8592b7cf3e50882b90969982ddfcc Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 21 Mar 2025 20:12:40 +0000 Subject: [PATCH 23/26] fix(invert_sentence) --- tests/invert_sentence_test/src/lib.rs | 51 ++++++++++++++++++++++++++ tests/invert_sentence_test/src/main.rs | 43 ---------------------- 2 files changed, 51 insertions(+), 43 deletions(-) create mode 100644 tests/invert_sentence_test/src/lib.rs delete mode 100644 tests/invert_sentence_test/src/main.rs diff --git a/tests/invert_sentence_test/src/lib.rs b/tests/invert_sentence_test/src/lib.rs new file mode 100644 index 00000000..68af0014 --- /dev/null +++ b/tests/invert_sentence_test/src/lib.rs @@ -0,0 +1,51 @@ +use invert_sentence::*; + +#[test] +fn empty_sentence() { + assert_eq!(invert_sentence(""), ""); +} + +#[test] +fn single_word_sentence() { + assert_eq!(invert_sentence("word"), "word"); +} + +#[test] +fn multiple_words_sentence() { + assert_eq!(invert_sentence("Rust is Awesome"), "Awesome is Rust"); +} + +#[test] +fn multiple_leading_and_trailing_whitespaces() { + assert_eq!( + invert_sentence(" word1 word2 "), + " word2 word1 " + ); +} + +#[test] +fn multiple_leading_and_trailing_whitespaces_repeated_words() { + assert_eq!( + invert_sentence(" word1 word2 word1 "), + " word1 word2 word1 " + ); + + assert_eq!( + invert_sentence(" word1 word2 word3 "), + " word3 word2 word1 " + ); +} + +#[test] +fn multiple_words_sentence_with_punctuation() { + let sentence = "Hello, World!"; + assert_eq!(invert_sentence(sentence), "World! Hello,"); +} + +#[test] +fn complex_example() { + assert_eq!( + invert_sentence("\tI'm alive\t\t!\t \t"), + "\t! alive\t\tI'm\t \t" + ); +} diff --git a/tests/invert_sentence_test/src/main.rs b/tests/invert_sentence_test/src/main.rs deleted file mode 100644 index 54e21fc3..00000000 --- a/tests/invert_sentence_test/src/main.rs +++ /dev/null @@ -1,43 +0,0 @@ -use invert_sentence::*; - -fn main() { - println!("{}", invert_sentence("Rust is Awesome")); - println!("{}", invert_sentence(" word1 word2 ")); - println!("{}", invert_sentence("Hello, World!")); -} - - -#[cfg(test)] -mod tests { - use invert_sentence::*; - - #[test] - fn single_word_sentence() { - let sentence = "word"; - assert_eq!(invert_sentence(sentence), "word"); - } - - #[test] - fn multiple_words_sentence() { - let sentence = "Rust is Awesome"; - assert_eq!(invert_sentence(sentence), "Awesome is Rust"); - } - - #[test] - fn multiple_leading_and_trailing_whitespaces() { - let sentence = " word1 word2 "; - assert_eq!(invert_sentence(sentence), " word2 word1 "); - } - - #[test] - fn multiple_words_sentence_with_punctuation() { - let sentence = "Hello, World!"; - assert_eq!(invert_sentence(sentence), "World! Hello,"); - } - - #[test] - fn empty_sentence() { - let sentence = ""; - assert_eq!(invert_sentence(sentence), ""); - } -} From fabe053e6681124b56fd60afd0f837cc80f35da3 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 21 Mar 2025 20:12:50 +0000 Subject: [PATCH 24/26] fix(invert_sentence) --- solutions/invert_sentence/src/lib.rs | 34 ++++++++++++---------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/solutions/invert_sentence/src/lib.rs b/solutions/invert_sentence/src/lib.rs index e4c43acf..30ea6bc9 100644 --- a/solutions/invert_sentence/src/lib.rs +++ b/solutions/invert_sentence/src/lib.rs @@ -1,25 +1,19 @@ +// This exercise is quite difficult and should come later in the piscine. + pub fn invert_sentence(string: &str) -> String { - let mut words: Vec<&str> = Vec::new(); - let mut word_start = None; + let words = string.split_ascii_whitespace(); + + let positions = string + .split_ascii_whitespace() + .map(|s| (s.as_ptr() as usize - string.as_ptr() as usize, s.len())) + .map(|(i, l)| i..=l + i - 1) + .rev(); - for (i, c) in string.char_indices() { - if c.is_whitespace() { - if let Some(start) = word_start { - words.push(&string[start..i]); - } - words.push(" "); // Preserve spaces - word_start = None; - } else { - if word_start.is_none() { - word_start = Some(i); - } - } - } + let mut acc = string.to_owned(); - if let Some(start) = word_start { - words.push(&string[start..]); - } + positions + .zip(words) + .for_each(|(r, s)| acc.replace_range(r, s)); - words.reverse(); - words.join("") + acc } From 1652b79491414a96f5aba520ae438852067394ea Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Mon, 24 Mar 2025 11:59:55 +0000 Subject: [PATCH 25/26] accidentally moved a package --- tests/{pig_latin_test => }/own_and_return_test/Cargo.toml | 0 tests/{pig_latin_test => }/own_and_return_test/src/main.rs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{pig_latin_test => }/own_and_return_test/Cargo.toml (100%) rename tests/{pig_latin_test => }/own_and_return_test/src/main.rs (100%) diff --git a/tests/pig_latin_test/own_and_return_test/Cargo.toml b/tests/own_and_return_test/Cargo.toml similarity index 100% rename from tests/pig_latin_test/own_and_return_test/Cargo.toml rename to tests/own_and_return_test/Cargo.toml diff --git a/tests/pig_latin_test/own_and_return_test/src/main.rs b/tests/own_and_return_test/src/main.rs similarity index 100% rename from tests/pig_latin_test/own_and_return_test/src/main.rs rename to tests/own_and_return_test/src/main.rs From 629ee9bf8dae444a5e96116884d6952a45f536bf Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Fri, 11 Apr 2025 14:23:59 +0100 Subject: [PATCH 26/26] fix(quest-04) --- tests/mobs_test/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/mobs_test/src/lib.rs b/tests/mobs_test/src/lib.rs index e54ca35f..8eebc89d 100644 --- a/tests/mobs_test/src/lib.rs +++ b/tests/mobs_test/src/lib.rs @@ -98,6 +98,16 @@ fn mob_recruit() { age: 37, }) ); + + mob.recruit(("Pedro", 14)); + + assert_eq!( + mob.members.get("Pedro"), + Some(&Member { + role: Role::Associate, + age: 14, + }) + ); } #[test]