diff --git a/exercises/practice/acronym/src/lib.rs b/exercises/practice/acronym/src/lib.rs index d31dd96c2e3..d4e11b19a1c 100644 --- a/exercises/practice/acronym/src/lib.rs +++ b/exercises/practice/acronym/src/lib.rs @@ -1,3 +1,39 @@ +fn acronym_from_camelcase(word: &str) -> String { + let mut res = String::new(); + res.push(word.chars().next().unwrap().to_uppercase().next().unwrap()); + + println!("res: {}", res); + + let remaining_capitals = word[1..].chars() + .filter(|c| c.is_uppercase()) + .collect::(); + + println!("remaining_capitals: {}", remaining_capitals); + + if remaining_capitals.chars().count() < word.chars().count() - 1 { + res.push_str(&remaining_capitals); + } + + + println!("res before return: {}", res); + + res + +} + pub fn abbreviate(phrase: &str) -> String { - todo!("Given the phrase '{phrase}', return its acronym"); + + let cleaned_phrase: String = phrase.chars() + .filter(|&c| c.is_alphanumeric() || c == '-' || c.is_whitespace()) + .map(|c| if c == '-' {' '} else {c}) + .collect(); + + println!("{}", cleaned_phrase); + + cleaned_phrase.split_whitespace() + // .map(|word| word.chars().next().unwrap().to_uppercase().to_string()) + .map(acronym_from_camelcase) + .collect::() + + // "PNG".to_string() } diff --git a/exercises/practice/affine-cipher/Cargo.toml b/exercises/practice/affine-cipher/Cargo.toml index 979844792ad..cd5d1e3a059 100644 --- a/exercises/practice/affine-cipher/Cargo.toml +++ b/exercises/practice/affine-cipher/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +once_cell = "1.18" \ No newline at end of file diff --git a/exercises/practice/affine-cipher/src/lib.rs b/exercises/practice/affine-cipher/src/lib.rs index 396aa99d042..4ecbe143a92 100644 --- a/exercises/practice/affine-cipher/src/lib.rs +++ b/exercises/practice/affine-cipher/src/lib.rs @@ -1,3 +1,9 @@ +use once_cell::sync::Lazy; + +static NB_LETTERS_IN_ALPHABET: Lazy = Lazy::new(|| { + 26 +}); + /// While the problem description indicates a return status of 1 should be returned on errors, /// it is much more common to return a `Result`, so we provide an error type for the result here. #[derive(Debug, Eq, PartialEq)] @@ -8,11 +14,77 @@ pub enum AffineCipherError { /// Encodes the plaintext using the affine cipher with key (`a`, `b`). Note that, rather than /// returning a return code, the more common convention in Rust is to return a `Result`. pub fn encode(plaintext: &str, a: i32, b: i32) -> Result { - todo!("Encode {plaintext} with the key ({a}, {b})"); + + match find_mmi(a, *NB_LETTERS_IN_ALPHABET) { + Some(_) => { + let res = plaintext + .chars() + .filter(|c| { + c.is_alphanumeric() + }) + .map(|c| { + c.to_ascii_lowercase() + }) + .map(|c| { + if c.is_ascii_digit() { + c + } + else { + let i = c as u8 - b'a'; + let letter_number: u8 = ((a * i as i32 + b ).rem_euclid(*NB_LETTERS_IN_ALPHABET)) as u8; + (b'a' + letter_number) as char + } + }) + .collect::>() + .chunks(5) + .map(|chunk| { + chunk.iter().collect::() + }) + .collect::>() + .join(" "); + + Ok(res) + }, + None => { + Err(AffineCipherError::NotCoprime(a)) + } + } } /// Decodes the ciphertext using the affine cipher with key (`a`, `b`). Note that, rather than /// returning a return code, the more common convention in Rust is to return a `Result`. pub fn decode(ciphertext: &str, a: i32, b: i32) -> Result { - todo!("Decode {ciphertext} with the key ({a}, {b})"); + match find_mmi(a, *NB_LETTERS_IN_ALPHABET) { + Some(mmi) => + { + let res = ciphertext + .chars() + .filter(|c| { + c.is_alphanumeric() + }) + .map(|c| { + if c.is_ascii_digit() { + c + } + else { + let y = c as u8 -b'a'; + // needs `rem_euclid()` because % can give negative results + let d: u8 = ((mmi * (y as i32 - b)).rem_euclid(*NB_LETTERS_IN_ALPHABET)) as u8; + (b'a' + d) as char + } + }) + .collect::(); + + Ok(res) + }, + None => { + // The MMI only exists if `a` and `NB_LETTERS_IN_ALPHABET` are coprime + Err(AffineCipherError::NotCoprime(a)) + } + } } + +fn find_mmi(a: i32, m: i32) -> Option { + // cannot be greater than `m` (otherwise would be cancel out in the mod) + (0..m).find(|x| (a * *x) % m == 1) +} \ No newline at end of file diff --git a/exercises/practice/all-your-base/src/lib.rs b/exercises/practice/all-your-base/src/lib.rs index 2803bcaf9b3..37faf6c3155 100644 --- a/exercises/practice/all-your-base/src/lib.rs +++ b/exercises/practice/all-your-base/src/lib.rs @@ -36,5 +36,37 @@ pub enum Error { /// However, your function must be able to process input with leading 0 digits. /// pub fn convert(number: &[u32], from_base: u32, to_base: u32) -> Result, Error> { - todo!("Convert {number:?} from base {from_base} to base {to_base}") + + if from_base < 2 { + return Err(Error::InvalidInputBase); + } + else if to_base < 2 { + return Err(Error::InvalidOutputBase); + } + + let mut res: Vec = Vec::new(); + let mut num = 0; + + for digit in number { + if *digit >= from_base { + return Err(Error::InvalidDigit(*digit)); + } + num = num * from_base + digit; + } + println!("num: {}", num); + + while num > 0 { + res.push(num % to_base); + num /= to_base; + } + + if res.is_empty() { + res.push(0); + return Ok(res); + } + + res.reverse(); + + Ok(res) + } diff --git a/exercises/practice/allergies/Cargo.toml b/exercises/practice/allergies/Cargo.toml index 41adcdd9377..33d177c607e 100644 --- a/exercises/practice/allergies/Cargo.toml +++ b/exercises/practice/allergies/Cargo.toml @@ -7,3 +7,5 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +strum = "0.27" +strum_macros = "0.27" diff --git a/exercises/practice/allergies/src/lib.rs b/exercises/practice/allergies/src/lib.rs index 6ded730ed54..a25c44310be 100644 --- a/exercises/practice/allergies/src/lib.rs +++ b/exercises/practice/allergies/src/lib.rs @@ -1,29 +1,36 @@ -pub struct Allergies; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; -#[derive(Debug, PartialEq, Eq)] +pub struct Allergies { + score: u32 +} + +#[derive(EnumIter, Debug, PartialEq, Eq, Clone, Copy)] pub enum Allergen { - Eggs, - Peanuts, - Shellfish, - Strawberries, - Tomatoes, - Chocolate, - Pollen, - Cats, + Eggs = 0b00000001, + Peanuts = 0b00000010, + Shellfish = 0b00000100, + Strawberries = 0b00001000, + Tomatoes = 0b00010000, + Chocolate = 0b00100000, + Pollen = 0b01000000, + Cats = 0b10000000, } impl Allergies { pub fn new(score: u32) -> Self { - todo!("Given the '{score}' score, construct a new Allergies struct."); + Self { + score + } } pub fn is_allergic_to(&self, allergen: &Allergen) -> bool { - todo!("Determine if the patient is allergic to the '{allergen:?}' allergen."); + self.score & *allergen as u32 != 0 } pub fn allergies(&self) -> Vec { - todo!( - "Return the list of allergens contained within the score with which the Allergies struct was made." - ); + Allergen::iter() + .filter(|a| self.is_allergic_to(a)) + .collect() } } diff --git a/exercises/practice/alphametics/Cargo.toml b/exercises/practice/alphametics/Cargo.toml index f2bd83000e4..bde6d233d22 100644 --- a/exercises/practice/alphametics/Cargo.toml +++ b/exercises/practice/alphametics/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +itertools = "0.14.0" \ No newline at end of file diff --git a/exercises/practice/alphametics/src/lib.rs b/exercises/practice/alphametics/src/lib.rs index a7eb2c2e2d4..7b5e380bf16 100644 --- a/exercises/practice/alphametics/src/lib.rs +++ b/exercises/practice/alphametics/src/lib.rs @@ -1,5 +1,60 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use itertools::Itertools; pub fn solve(input: &str) -> Option> { - todo!("Solve the alphametic {input:?}") + println!("{}", input); + + let firsts: HashSet = input + .split(&['+', '=']) + .filter_map(|s| s.trim().chars().next()) + .collect(); + + println!("{:?}", firsts); + + let (letters, factors) = parse(input); + + println!("letter: {:?}, factors: {:?}", letters, factors); + + for perm in (0..=9).permutations(letters.len()) { + if cal_perm_sum(&perm, &factors) == 0 + && !perm.iter().enumerate().any(|(i, v)| firsts.contains(letters.get(i).unwrap()) && *v == 0) { + return Some(HashMap::from_iter( + perm.iter().enumerate().map(|(i, v)| (*letters.get(i).unwrap(), *v as u8)) + )); + } + } + + None +} + +fn cal_perm_sum(perm: &[i64], factors: &[i64]) -> i64 { + perm.iter().enumerate().map(|(i, v)| *v * factors.get(i).unwrap()).sum() } + +// 26 + 26 + 766 = 1970 + +fn parse(input: &str) -> (Vec, Vec) { + let mut factors = HashMap::new(); + let mut pos = 0; + let mut sign = -1; + + for c in input.chars().filter(|c| !c.is_whitespace()).rev() { + match c { + '=' => { + sign = 1; + pos = 0; + }, + '+' => { + pos = 0; + }, + _ => { + let factor = factors.entry(c).or_insert(0); + *factor += sign * 10_i64.pow(pos); + pos += 1; + } + } + } + + factors.iter().unzip() + +} \ No newline at end of file diff --git a/exercises/practice/anagram/src/lib.rs b/exercises/practice/anagram/src/lib.rs index f029d044911..69169801324 100644 --- a/exercises/practice/anagram/src/lib.rs +++ b/exercises/practice/anagram/src/lib.rs @@ -1,5 +1,42 @@ use std::collections::HashSet; -pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&str]) -> HashSet<&'a str> { - todo!("For the '{word}' word find anagrams among the following words: {possible_anagrams:?}"); +fn is_anagram(word :&str, candidate :&str) -> bool { + if word.eq_ignore_ascii_case(candidate) { + return false; + } + + // anagrams have same length + if word.len() != candidate.len() { + return false; + } + + let mut word_c :Vec = word.chars().collect(); + let mut candidate_c :Vec = candidate.chars().collect(); + + word_c.sort_unstable(); + candidate_c.sort_unstable(); + + word_c == candidate_c + +} + +pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&'a str]) -> HashSet<&'a str> { + println!("{}", is_anagram(word, possible_anagrams[0])); + + // let mut res = HashSet::new(); + + let binding = word.to_lowercase(); + let word_lower: &str = binding.as_str(); + + // for possible_anagram in possible_anagrams { + // if is_anagram(word_lower, possible_anagram.to_lowercase().as_str()) { + // res.insert(*possible_anagram); + // } + // } + + possible_anagrams.iter().filter(|x| { + is_anagram(word_lower, x.to_lowercase().as_str()) + }).map(|x| *x).collect::>() + + // res } diff --git a/exercises/practice/armstrong-numbers/src/lib.rs b/exercises/practice/armstrong-numbers/src/lib.rs index 91a62532da8..1ba0c3cdc00 100644 --- a/exercises/practice/armstrong-numbers/src/lib.rs +++ b/exercises/practice/armstrong-numbers/src/lib.rs @@ -1,3 +1,6 @@ pub fn is_armstrong_number(num: u32) -> bool { - todo!("true if {num} is an armstrong number") + let nb_digit = num.to_string().chars().count(); + num.to_string().chars() + .map(|c| c.to_digit(10).unwrap().pow(nb_digit as u32)) + .sum::() == num } diff --git a/exercises/practice/atbash-cipher/Cargo.toml b/exercises/practice/atbash-cipher/Cargo.toml index f2f21e9732a..73811f269a4 100644 --- a/exercises/practice/atbash-cipher/Cargo.toml +++ b/exercises/practice/atbash-cipher/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +once_cell = "1.18" diff --git a/exercises/practice/atbash-cipher/src/lib.rs b/exercises/practice/atbash-cipher/src/lib.rs index 5f63c8b857b..94fe70aa28f 100644 --- a/exercises/practice/atbash-cipher/src/lib.rs +++ b/exercises/practice/atbash-cipher/src/lib.rs @@ -1,9 +1,44 @@ +use once_cell::sync::Lazy; + +static ALPHABET: Lazy<&str> = Lazy::new(|| { + "abcdefghijklmnopqrstuvwxyz" +}); + /// "Encipher" with the Atbash cipher. pub fn encode(plain: &str) -> String { - todo!("Encoding of {plain:?} in Atbash cipher."); + let nb_letters = ALPHABET.chars().count(); + + plain + .chars() + .filter(|c| { + c.is_alphanumeric() + }) + .map(|c| { + c.to_ascii_lowercase() + }) + .map(|c| { + if c.is_numeric() { + c + } + else { + ALPHABET.chars().nth(nb_letters - 1 - (c as u8 - b'a') as usize).unwrap() + } + }) + .collect::>() + .chunks(5) + .map(|chunk| { + chunk.iter().collect::() + }) + .collect::>() + .join(" ") } /// "Decipher" with the Atbash cipher. pub fn decode(cipher: &str) -> String { - todo!("Decoding of {cipher:?} in Atbash cipher."); + encode(cipher) + .chars() + .filter(|c| { + !c.is_whitespace() + }) + .collect() } diff --git a/exercises/practice/binary-search/src/lib.rs b/exercises/practice/binary-search/src/lib.rs index fbdd6d6fd53..582d36f722a 100644 --- a/exercises/practice/binary-search/src/lib.rs +++ b/exercises/practice/binary-search/src/lib.rs @@ -1,5 +1,50 @@ -pub fn find(array: &[i32], key: i32) -> Option { - todo!( - "Using the binary search algorithm, find the element '{key}' in the array '{array:?}' and return its index." - ); -} +use std::cmp::Ordering; + +// pub fn find(array: &[i32], key: i32) -> Option { + +// println!("{:?}", array); +// println!("{}", 1 /2 ); + +// if array.len() == 0 { +// return None; +// } + +// let index_value_middle = array.len() / 2; +// let value_middle = array.get(index_value_middle).unwrap(); +// match key.cmp(value_middle) { +// Ordering::Less => { +// return find(&array[0..index_value_middle], key); +// }, +// Ordering::Greater => { +// return find(&array[index_value_middle+1..], key).map(|i| i + index_value_middle + 1); +// }, +// Ordering::Equal => { +// return Some(index_value_middle); +// } +// } + +// } + +pub fn find, T: Ord>(array: R, key: T) -> Option { + + let array = array.as_ref(); + + if array.is_empty() { + return None; + } + + let index_value_middle = array.len() / 2; + let value_middle = array.get(index_value_middle).unwrap(); + match key.cmp(value_middle) { + Ordering::Less => { + find(&array[0..index_value_middle], key) + }, + Ordering::Greater => { + find(&array[index_value_middle+1..], key).map(|i| i + index_value_middle + 1) + }, + Ordering::Equal => { + Some(index_value_middle) + } + } + +} \ No newline at end of file diff --git a/exercises/practice/bob/src/lib.rs b/exercises/practice/bob/src/lib.rs index 833f2dfcaa0..270de7453c7 100644 --- a/exercises/practice/bob/src/lib.rs +++ b/exercises/practice/bob/src/lib.rs @@ -1,3 +1,19 @@ pub fn reply(message: &str) -> &str { - todo!("have Bob reply to the incoming message: {message}") + + let trimed = message.trim(); + + if trimed.is_empty() { + return "Fine. Be that way!" + } + + let is_question = trimed.ends_with("?"); + let is_yelled_vec: Vec = trimed.chars().filter(|c| c.is_alphabetic()).collect(); + let is_yelled = is_yelled_vec.iter().all(|c| c.is_uppercase()) && !is_yelled_vec.is_empty(); + + match (is_question, is_yelled) { + (true, true) => "Calm down, I know what I'm doing!", + (true, false) => "Sure.", + (false, true) => "Whoa, chill out!", + (false, false) => "Whatever." + } } diff --git a/exercises/practice/bottle-song/src/lib.rs b/exercises/practice/bottle-song/src/lib.rs index 2033677b599..f0d295e8a27 100644 --- a/exercises/practice/bottle-song/src/lib.rs +++ b/exercises/practice/bottle-song/src/lib.rs @@ -1,3 +1,45 @@ +fn capitalize(s: &str) -> String { + let mut chars = s.chars(); + match chars.next() { + Some(first) => first.to_uppercase().collect::() + chars.as_str(), + None => String::new(), // Handle empty string case + } +} +fn number_to_word(n: u32) -> &'static str { + match n { + 1 => "one", + 2 => "two", + 3 => "three", + 4 => "four", + 5 => "five", + 6 => "six", + 7 => "seven", + 8 => "eight", + 9 => "nine", + 10 => "ten", + _ => "unknown", // Handle numbers out of range + } +} +pub fn verse(n_bottles: u32 ) -> String { + match n_bottles { + 1 => "One green bottle hanging on the wall,\n\ + One green bottle hanging on the wall,\n\ + And if one green bottle should accidentally fall,\n\ + There'll be no green bottles hanging on the wall.".to_string(), + 2 => "Two green bottles hanging on the wall,\n\ + Two green bottles hanging on the wall,\n\ + And if one green bottle should accidentally fall,\n\ + There'll be one green bottle hanging on the wall.".to_string(), + _ => format!("{} green bottles hanging on the wall,\n\ + {} green bottles hanging on the wall,\n\ + And if one green bottle should accidentally fall,\n\ + There'll be {} green bottles hanging on the wall.", capitalize(number_to_word(n_bottles)), capitalize(number_to_word(n_bottles)), number_to_word(n_bottles-1)) + } +} + pub fn recite(start_bottles: u32, take_down: u32) -> String { - todo!("Return the bottle song starting at {start_bottles} and taking down {take_down} bottles") + (0..take_down) + .map(|i| verse(start_bottles-i)) + .collect::>() + .join("\n\n") } diff --git a/exercises/practice/bowling/src/lib.rs b/exercises/practice/bowling/src/lib.rs index 3163e61202c..beed69062bf 100644 --- a/exercises/practice/bowling/src/lib.rs +++ b/exercises/practice/bowling/src/lib.rs @@ -4,18 +4,57 @@ pub enum Error { GameComplete, } -pub struct BowlingGame {} +pub struct BowlingGame { + throws: Vec, + second_throw_in_frame: bool +} impl BowlingGame { pub fn new() -> Self { - todo!(); + BowlingGame { + throws: Vec::new(), + second_throw_in_frame: false + } } pub fn roll(&mut self, pins: u16) -> Result<(), Error> { - todo!("Record that {pins} pins have been scored"); + if pins > 10 || (self.second_throw_in_frame && (pins + self.throws.last().unwrap()) > 10 ){ + return Result::Err(Error::NotEnoughPinsLeft); + } + if self.score().is_some() { + Result::Err(Error::GameComplete) + } + else { + self.throws.push(pins); + self.second_throw_in_frame = if pins == 10 {false} else {!self.second_throw_in_frame}; + Result::Ok(()) + } } pub fn score(&self) -> Option { - todo!("Return the score if the game is complete, or None if not."); + let mut total: u16 = 0; + let mut frame = 0; + + let throws = &self.throws; + + for _ in 0..10 { + if let (Some(&first), Some(&second)) = (throws.get(frame), throws.get(frame+1)) { + total += first + second; + + if first == 10 || first + second == 10 { + if let Some(&third) = throws.get(frame+2) { + total += third; + } + else { + return None; + } + } + frame += if first == 10 {1} else {2}; + } + else { + return None; + } + } + Some(total) } } diff --git a/exercises/practice/clock/src/lib.rs b/exercises/practice/clock/src/lib.rs index c625f73de1a..52fb06f6196 100644 --- a/exercises/practice/clock/src/lib.rs +++ b/exercises/practice/clock/src/lib.rs @@ -1,11 +1,38 @@ -pub struct Clock; +use std::fmt; + +#[derive(Debug, PartialEq)] +pub struct Clock { + hours: i32, + minutes: i32, +} +const HOURS_PER_DAY: i32 = 24; +const MINUTES_PER_HOUR: i32 = 60; +const MINUTES_PER_DAY: i32 = MINUTES_PER_HOUR * HOURS_PER_DAY; impl Clock { pub fn new(hours: i32, minutes: i32) -> Self { - todo!("Construct a new Clock from {hours} hours and {minutes} minutes"); + let total_minutes = total_minutes(hours, minutes); + let hours = total_minutes / MINUTES_PER_HOUR; + let minutes = total_minutes % MINUTES_PER_HOUR; + Self { hours, minutes } } pub fn add_minutes(&self, minutes: i32) -> Self { - todo!("Add {minutes} minutes to existing Clock time"); + Self::new(self.hours, self.minutes + minutes) } } + +impl fmt::Display for Clock { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:02}:{:02}", self.hours, self.minutes) + } +} + +fn total_minutes(hours: i32, minutes: i32) -> i32 { + let total_minutes = (hours * MINUTES_PER_HOUR + minutes) % MINUTES_PER_DAY; + if total_minutes >= 0 { + total_minutes + } else { + total_minutes + MINUTES_PER_DAY + } +} \ No newline at end of file diff --git a/exercises/practice/collatz-conjecture/src/lib.rs b/exercises/practice/collatz-conjecture/src/lib.rs index 84f178225f9..9115e7e6c02 100644 --- a/exercises/practice/collatz-conjecture/src/lib.rs +++ b/exercises/practice/collatz-conjecture/src/lib.rs @@ -1,3 +1,22 @@ pub fn collatz(n: u64) -> Option { - todo!("return Some(x) where x is the number of steps required to reach 1 starting with {n}") + if n == 0 { + return None; + } + + let mut n = n; + let mut counter = 0; + + while n != 1 { + match n % 2 == 0 { + true => { + n /= 2; + }, + false => { + n = n*3 + 1; + } + + } + counter +=1 ; + } + Some(counter) } diff --git a/exercises/practice/crypto-square/src/lib.rs b/exercises/practice/crypto-square/src/lib.rs index c22623ef250..e5009c27cca 100644 --- a/exercises/practice/crypto-square/src/lib.rs +++ b/exercises/practice/crypto-square/src/lib.rs @@ -1,3 +1,58 @@ pub fn encrypt(input: &str) -> String { - todo!("Encrypt {input:?} using a square code") + + let normalized: Vec = input + .chars() + .filter(|c| { + c.is_alphanumeric() + }) + .map(|c| { + c.to_ascii_lowercase() + }) + .collect(); + + // Early return + if normalized.is_empty() { + return "".to_string(); + } + + let nb_normalized_chars = normalized.len(); + let nb_row = (nb_normalized_chars as f64).sqrt() as usize; + let nb_col = if nb_row * nb_row < nb_normalized_chars { + nb_row + 1 + } + else { + nb_row + }; + let nb_row = if nb_row * nb_col < nb_normalized_chars { + nb_row + 1 + } + else { + nb_row + }; + + println!("nb_row: {}, nb_col: {}", nb_row, nb_col); + + let intermediate_representation: Vec> = normalized + .chunks(nb_col) + .map(|chunk| { + let mut s: String = chunk.iter().collect(); + while s.chars().count() < nb_col { + s.push(' '); + } + s + }) + .map(|s| { + s.chars().collect() + }) + .collect(); + + let encoded: Vec = (0..nb_col).map(|col| { + (0..nb_row).map(|row| { + intermediate_representation[row][col] + }) + .collect() + }) + .collect(); + + encoded.join(" ") } diff --git a/exercises/practice/custom-set/src/lib.rs b/exercises/practice/custom-set/src/lib.rs index 3cde4569ce2..a9cb0ee5728 100644 --- a/exercises/practice/custom-set/src/lib.rs +++ b/exercises/practice/custom-set/src/lib.rs @@ -1,47 +1,56 @@ +use std::collections::HashSet; +use std::hash::Hash; + #[derive(Debug, PartialEq, Eq)] -pub struct CustomSet { +pub struct CustomSet { // We fake using T here, so the compiler does not complain that // "parameter `T` is never used". Delete when no longer needed. - phantom: std::marker::PhantomData, + elements: HashSet } -impl CustomSet { +impl CustomSet { pub fn new(_input: &[T]) -> Self { - todo!(); + Self { + elements: _input.iter().cloned().collect() + } } pub fn contains(&self, _element: &T) -> bool { - todo!(); + self.elements.contains(_element) } pub fn add(&mut self, _element: T) { - todo!(); + self.elements.insert(_element); } pub fn is_subset(&self, _other: &Self) -> bool { - todo!(); + self.elements.is_subset(&_other.elements) } pub fn is_empty(&self) -> bool { - todo!(); + self.elements.is_empty() } pub fn is_disjoint(&self, _other: &Self) -> bool { - todo!(); + self.elements.is_disjoint(&_other.elements) } #[must_use] pub fn intersection(&self, _other: &Self) -> Self { - todo!(); + Self { + elements: self.elements.intersection(&_other.elements).cloned().collect() + } } #[must_use] pub fn difference(&self, _other: &Self) -> Self { - todo!(); + Self { + elements: self.elements.difference(&_other.elements).cloned().collect() + } } #[must_use] pub fn union(&self, _other: &Self) -> Self { - todo!(); + Self { elements: self.elements.union(&_other.elements).cloned().collect() } } } diff --git a/exercises/practice/diamond/Cargo.toml b/exercises/practice/diamond/Cargo.toml index 436ce7a0b8a..9bd739341ed 100644 --- a/exercises/practice/diamond/Cargo.toml +++ b/exercises/practice/diamond/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +once_cell = "1.18" \ No newline at end of file diff --git a/exercises/practice/diamond/src/lib.rs b/exercises/practice/diamond/src/lib.rs index db78003f8a2..33012ee4827 100644 --- a/exercises/practice/diamond/src/lib.rs +++ b/exercises/practice/diamond/src/lib.rs @@ -1,3 +1,38 @@ +use once_cell::sync::Lazy; + +static ALPHABET: Lazy<&str> = Lazy::new(|| { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +}); + pub fn get_diamond(c: char) -> Vec { - todo!("Return the vector of strings which represent the diamond with particular char {c}"); + + let mut res: Vec = Vec::new(); + + if let Some(c_index) = ALPHABET.find(c) { + // Special case for A + res.push(format!("{}A{}", + " ".repeat(c_index), + " ".repeat(c_index))); + + // Then, letters up to `c` + for i in 1..=c_index { + let letter = ALPHABET.chars().nth(i).unwrap(); + res.push(format!("{}{}{}{}{}", + " ".repeat(c_index-i), + letter, + " ".repeat(2*i-1), + letter, + " ".repeat(c_index-i))); + } + + // Generate the symmetric bottom half + let res2: Vec = res[..res.len()-1] + .iter() + .rev() + .cloned() + .collect(); + + res.extend(res2); + } + res } diff --git a/exercises/practice/difference-of-squares/src/lib.rs b/exercises/practice/difference-of-squares/src/lib.rs index fdf2e93d8a6..c4a1fabcf64 100644 --- a/exercises/practice/difference-of-squares/src/lib.rs +++ b/exercises/practice/difference-of-squares/src/lib.rs @@ -1,11 +1,17 @@ pub fn square_of_sum(n: u32) -> u32 { - todo!("square of sum of 1...{n}") + sum(n).pow(2) } pub fn sum_of_squares(n: u32) -> u32 { - todo!("sum of squares of 1...{n}") + (1..=n) + .map(|m| m*m) + .sum() } pub fn difference(n: u32) -> u32 { - todo!("difference between square of sum of 1...{n} and sum of squares of 1...{n}") + square_of_sum(n) - sum_of_squares(n) +} + +fn sum(n: u32) -> u32 { + n * (n+1) / 2 } diff --git a/exercises/practice/dot-dsl/src/lib.rs b/exercises/practice/dot-dsl/src/lib.rs index 3f2514dc694..554ae7e470e 100644 --- a/exercises/practice/dot-dsl/src/lib.rs +++ b/exercises/practice/dot-dsl/src/lib.rs @@ -1,9 +1,109 @@ pub mod graph { - pub struct Graph; + + use std::collections::HashMap; + + use self::graph_items::node::Node; + use self::graph_items::edge::Edge; + + #[derive(Default)] + pub struct Graph{ + pub nodes: Vec, + pub edges: Vec, + pub attrs: HashMap + } impl Graph { pub fn new() -> Self { - todo!("Construct a new Graph struct."); + Self::default() + } + + pub fn with_nodes(mut self, nodes: &[Node]) -> Self { + self.nodes = nodes.to_vec(); + self + } + + pub fn with_edges(mut self, edges: &[Edge]) -> Self { + self.edges = edges.to_vec(); + self + } + + pub fn with_attrs(mut self, attrs: &[(&str, &str)]) -> Self { + self.attrs = attrs + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + self + } + + pub fn node(&self, name: &str) -> Option<&Node> { + self.nodes.iter().find(|node| node.name == name) + } + + } + + pub mod graph_items { + pub mod edge { + use std::collections::HashMap; + + #[derive(Default, Clone, PartialEq, Debug)] + pub struct Edge { + pub from: String, + pub to: String, + pub attrs: HashMap + } + + impl Edge { + pub fn new(from: &str, to: &str) -> Self { + Edge { + from: from.to_string(), + to: to.to_string(), + ..Default::default() + } + } + + pub fn with_attrs(mut self, attrs: &[(&str, &str)]) -> Self { + self.attrs = attrs + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + self + } + + pub fn attr(&self, key: &str) -> Option<&str> { + self.attrs.get(key).map(|x| x.as_str()) + } + } + } + + pub mod node { + use std::collections::HashMap; + + #[derive(Default, Clone, PartialEq, Debug)] + pub struct Node { + pub name: String, + pub attrs: HashMap + } + + impl Node { + pub fn new(name: &str) -> Node { + Node { + name: name.to_string(), + ..Default::default() + } + } + + pub fn with_attrs(mut self, attrs: &[(&str, &str)]) -> Self { + self.attrs = attrs + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + self + } + + pub fn attr(&self, key: &str) -> Option<&str> { + self.attrs.get(key).map(|x| x.as_str()) + } + } } } } diff --git a/exercises/practice/eliuds-eggs/src/lib.rs b/exercises/practice/eliuds-eggs/src/lib.rs index 0565f7348f6..734b9adfdbb 100644 --- a/exercises/practice/eliuds-eggs/src/lib.rs +++ b/exercises/practice/eliuds-eggs/src/lib.rs @@ -1,3 +1,10 @@ pub fn egg_count(display_value: u32) -> usize { - todo!("count the eggs in {display_value}") + let mut display_value = display_value; + let mut count: u32 = 0; + + while display_value != 0 { + count += display_value & 1; + display_value >>= 1; + } + count.try_into().unwrap() } diff --git a/exercises/practice/etl/src/lib.rs b/exercises/practice/etl/src/lib.rs index 25e941f276a..33ac9057191 100644 --- a/exercises/practice/etl/src/lib.rs +++ b/exercises/practice/etl/src/lib.rs @@ -1,5 +1,14 @@ use std::collections::BTreeMap; pub fn transform(h: &BTreeMap>) -> BTreeMap { - todo!("How will you transform the tree {h:?}?") + let mut res: BTreeMap = BTreeMap::new(); + + for (score, chars) in h { + for c in chars { + res.insert(c.to_lowercase().next().unwrap(), *score); + } + } + + res + } diff --git a/exercises/practice/gigasecond/src/lib.rs b/exercises/practice/gigasecond/src/lib.rs index 2dbb9ab9a11..b5363094bd6 100644 --- a/exercises/practice/gigasecond/src/lib.rs +++ b/exercises/practice/gigasecond/src/lib.rs @@ -1,6 +1,7 @@ use time::PrimitiveDateTime as DateTime; +use time::Duration; // Returns a DateTime one billion seconds after start. pub fn after(start: DateTime) -> DateTime { - todo!("What time is a gigasecond later than {start}"); + start + Duration::seconds(1000000000) } diff --git a/exercises/practice/grade-school/src/lib.rs b/exercises/practice/grade-school/src/lib.rs index da3f53603d2..de5cfa41fc8 100644 --- a/exercises/practice/grade-school/src/lib.rs +++ b/exercises/practice/grade-school/src/lib.rs @@ -1,16 +1,25 @@ -pub struct School {} +use std::collections::{BTreeMap, BTreeSet}; + +#[derive(Default)] +pub struct School { + roster: BTreeMap +} impl School { pub fn new() -> School { - todo!() + School{ + ..Default::default() + } } pub fn add(&mut self, grade: u32, student: &str) { - todo!("Add {student} to the roster for {grade}") + self.roster.entry(student.to_string()).or_insert(grade); } pub fn grades(&self) -> Vec { - todo!() + let mut res:Vec = self.roster.values().cloned().collect::>().into_iter().collect(); + res.sort(); + res } // If `grade` returned a reference, `School` would be forced to keep a `Vec` @@ -18,6 +27,13 @@ impl School { // the internal structure can be completely arbitrary. The tradeoff is that some data // must be copied each time `grade` is called. pub fn grade(&self, grade: u32) -> Vec { - todo!("Return the list of students in {grade}") + self.roster.iter().filter_map(|(n, &g)| { + if g == grade { + Some(n.clone()) + } + else { + None + } + }).collect() } } diff --git a/exercises/practice/grains/src/lib.rs b/exercises/practice/grains/src/lib.rs index 8eeccb68bc2..28b7baa5a8d 100644 --- a/exercises/practice/grains/src/lib.rs +++ b/exercises/practice/grains/src/lib.rs @@ -1,7 +1,7 @@ pub fn square(s: u32) -> u64 { - todo!("grains of rice on square {s}"); + 2_u64.pow(s-1) } pub fn total() -> u64 { - todo!(); + (1..=64).map(square).sum::() } diff --git a/exercises/practice/hamming/src/lib.rs b/exercises/practice/hamming/src/lib.rs index 391af3c5261..ce414e651f6 100644 --- a/exercises/practice/hamming/src/lib.rs +++ b/exercises/practice/hamming/src/lib.rs @@ -1,5 +1,19 @@ /// Return the Hamming distance between the strings, /// or None if the lengths are mismatched. pub fn hamming_distance(s1: &str, s2: &str) -> Option { - todo!("What is the Hamming Distance between {s1} and {s2}"); + + let mut res = 0; + + if s1.chars().count() != s2.chars().count() { + return None; + } + else { + for (c1, c2) in s1.chars().zip(s2.chars()) { + if c1 != c2 { + res += 1; + } + } + } + Some(res) + } diff --git a/exercises/practice/hello-world/src/lib.rs b/exercises/practice/hello-world/src/lib.rs index 05f6f4a94e4..5c2db6db34e 100644 --- a/exercises/practice/hello-world/src/lib.rs +++ b/exercises/practice/hello-world/src/lib.rs @@ -1,4 +1,4 @@ // &'static is a "lifetime specifier", something you'll learn more about later pub fn hello() -> &'static str { - "Goodbye, Mars!" + "Hello, World!" } diff --git a/exercises/practice/high-scores/src/lib.rs b/exercises/practice/high-scores/src/lib.rs index 07f513ef658..9d44e153e64 100644 --- a/exercises/practice/high-scores/src/lib.rs +++ b/exercises/practice/high-scores/src/lib.rs @@ -1,24 +1,31 @@ #[derive(Debug)] -pub struct HighScores; +pub struct HighScores<'a> { + scores: &'a [u32] +} -impl HighScores { - pub fn new(scores: &[u32]) -> Self { - todo!("Construct a HighScores struct, given the scores: {scores:?}") +impl<'a> HighScores<'a> { + pub fn new(scores: &'a [u32]) -> Self { + Self { + scores + } } pub fn scores(&self) -> &[u32] { - todo!("Return all the scores as a slice") + self.scores } pub fn latest(&self) -> Option { - todo!("Return the latest (last) score") + self.scores.last().copied() } pub fn personal_best(&self) -> Option { - todo!("Return the highest score") + self.scores.iter().max().copied() } pub fn personal_top_three(&self) -> Vec { - todo!("Return 3 highest scores") + // let mut scores_vec = self.scores.iter().copied().collect::>(); + let mut scores_vec = self.scores.to_vec(); + scores_vec.sort_unstable_by(|a, b| b.cmp(a)); + scores_vec.iter().take(3).copied().collect() } } diff --git a/exercises/practice/isbn-verifier/src/lib.rs b/exercises/practice/isbn-verifier/src/lib.rs index c500b0bf5ed..47ca62c56dd 100644 --- a/exercises/practice/isbn-verifier/src/lib.rs +++ b/exercises/practice/isbn-verifier/src/lib.rs @@ -1,4 +1,37 @@ /// Determines whether the supplied string is a valid ISBN number pub fn is_valid_isbn(isbn: &str) -> bool { - todo!("Is {isbn:?} a valid ISBN number?"); + if isbn.is_empty() { + return false; + } + + if isbn[..isbn.len() - 1].chars().any(|c| !( c.is_ascii_digit() || c=='-')) { + return false; + } + + if !isbn.chars().last().is_some_and(|c| c.is_ascii_digit() || c == 'X') { + return false; + } + + let temp: String = isbn.chars().filter(|&c| c!= '-').collect(); + + println!("{}", temp); + + if temp.chars().count() != 10 { + false + } + else { + let qty:u32 = temp.chars().enumerate().map(|(i, c)| { + if i<10-1 { + c.to_digit(10).unwrap() * (10-i as u32) + } + else if c == 'X' { + 10 + } + else { + c.to_digit(10).unwrap() + } + }).sum(); + println!("{}", qty); + qty % 11 == 0 + } } diff --git a/exercises/practice/isogram/src/lib.rs b/exercises/practice/isogram/src/lib.rs index 620281037dd..28cce2217d9 100644 --- a/exercises/practice/isogram/src/lib.rs +++ b/exercises/practice/isogram/src/lib.rs @@ -1,3 +1,19 @@ +use std::collections::HashSet; + pub fn check(candidate: &str) -> bool { - todo!("Is {candidate} an isogram?"); + let cleaned_candidate:String = candidate.chars() + .filter(|c| c.is_ascii_alphabetic()) + .map(|c| c.to_ascii_lowercase()) + .collect(); + + let mut char_set:HashSet = HashSet::new(); + + for c in cleaned_candidate.chars() { + if char_set.contains(&c) { + return false + } + char_set.insert(c); + } + + true } diff --git a/exercises/practice/kindergarten-garden/src/lib.rs b/exercises/practice/kindergarten-garden/src/lib.rs index 45106de9b7e..f92cfb35cda 100644 --- a/exercises/practice/kindergarten-garden/src/lib.rs +++ b/exercises/practice/kindergarten-garden/src/lib.rs @@ -1,3 +1,42 @@ +const CHILDREN: [&str; 12] = [ + "Alice", + "Bob", + "Charlie", + "David", + "Eve", + "Fred", + "Ginny", + "Harriet", + "Ileana", + "Joseph", + "Kincaid", + "Larry" +]; + pub fn plants(diagram: &str, student: &str) -> Vec<&'static str> { - todo!("based on the {diagram}, determine the plants the {student} is responsible for"); + // let mut resres = Vec::new(); + + let idx = CHILDREN.iter().position(|&child| child == student).unwrap() * 2; + + println!("{}", idx); + + diagram.lines().flat_map(|line| { + println!("{}", line); + line[idx..=idx+1].chars().map(|c| + match c { + 'G' => "grass", + 'C' => "clover", + 'R' => "radishes", + _ => "violets", + } + ) + }) + .collect() + + // resres.push("grass"); + + // println!("{}", diagram); + + // resres + } diff --git a/exercises/practice/largest-series-product/src/lib.rs b/exercises/practice/largest-series-product/src/lib.rs index 8b4ce6d7328..1aa794280f3 100644 --- a/exercises/practice/largest-series-product/src/lib.rs +++ b/exercises/practice/largest-series-product/src/lib.rs @@ -5,5 +5,37 @@ pub enum Error { } pub fn lsp(string_digits: &str, span: usize) -> Result { - todo!("largest series product of a span of {span} digits in {string_digits}"); + + if span > string_digits.len() { + return Err(Error::SpanTooLong); + } + + // `windows(0) not allowed`, so handling it here + if span == 0 { + return Ok(1); + } + + let mut max_product: u64 = 0; + + for window in string_digits + .chars() + .collect::>() + .windows(span) { + let mut product: u64 = 1; + + for c in window { + if let Some(d) = c.to_digit(10) { + product *= d as u64; + } + else { + return Err(Error::InvalidDigit(*c)); + } + } + + if product > max_product { + max_product = product; + } + } + + Ok(max_product) } diff --git a/exercises/practice/leap/src/lib.rs b/exercises/practice/leap/src/lib.rs index edeefea4dc2..c97f320fac7 100644 --- a/exercises/practice/leap/src/lib.rs +++ b/exercises/practice/leap/src/lib.rs @@ -1,3 +1,9 @@ pub fn is_leap_year(year: u64) -> bool { - todo!("true if {year} is a leap year") + if year % 100 == 0 { + if year % 400 == 0 { + return true; + } + return false; + } + year % 4 == 0 } diff --git a/exercises/practice/list-ops/src/lib.rs b/exercises/practice/list-ops/src/lib.rs index a67c02ac18c..2e22e09600a 100644 --- a/exercises/practice/list-ops/src/lib.rs +++ b/exercises/practice/list-ops/src/lib.rs @@ -4,9 +4,11 @@ where I: Iterator, J: Iterator, { - // this empty iterator silences a compiler complaint that - // () doesn't implement Iterator - std::iter::from_fn(|| todo!()) + let mut res: Vec = _a.collect(); + for el in _b { + res.push(el); + } + res.into_iter() } /// Combines all items in all nested iterators inside into one flattened iterator @@ -15,9 +17,14 @@ where I: Iterator, I::Item: Iterator, { - // this empty iterator silences a compiler complaint that - // () doesn't implement Iterator - std::iter::from_fn(|| todo!()) + let mut res: Vec<::Item> = Vec::new(); + for it_el in _nested_iter { + for el in it_el { + res.push(el); + } + } + + res.into_iter() } /// Returns an iterator of all items in iter for which `predicate(item)` is true @@ -26,13 +33,25 @@ where I: Iterator, F: Fn(&I::Item) -> bool, { - // this empty iterator silences a compiler complaint that - // () doesn't implement Iterator - std::iter::from_fn(|| todo!()) + let mut res: Vec = Vec::new(); + + for el in _iter { + if _predicate(&el) { + res.push(el); + } + } + + res.into_iter() } pub fn length(_iter: I) -> usize { - todo!("return the total number of items within iter") + let mut res: usize = 0; + + for _ in _iter { + res += 1; + } + + res } /// Returns an iterator of the results of applying `function(item)` on all iter items @@ -41,9 +60,13 @@ where I: Iterator, F: Fn(I::Item) -> U, { - // this empty iterator silences a compiler complaint that - // () doesn't implement Iterator - std::iter::from_fn(|| todo!()) + let mut res: Vec = Vec::new(); + + for el in _iter { + res.push(_function(el)); + } + + res.into_iter() } pub fn foldl(mut _iter: I, _initial: U, _function: F) -> U @@ -51,7 +74,13 @@ where I: Iterator, F: Fn(U, I::Item) -> U, { - todo!("starting with initial, fold (reduce) each iter item into the accumulator from the left") + let mut res: U = _initial; + + for el in _iter { + res = _function(res, el); + } + + res } pub fn foldr(mut _iter: I, _initial: U, _function: F) -> U @@ -59,12 +88,26 @@ where I: DoubleEndedIterator, F: Fn(U, I::Item) -> U, { - todo!("starting with initial, fold (reduce) each iter item into the accumulator from the right") + let mut res: U = _initial; + + while let Some(el) = _iter.next_back() { + res = _function(res, el); + } + + res } /// Returns an iterator with all the original items, but in reverse order pub fn reverse(_iter: I) -> impl Iterator { // this empty iterator silences a compiler complaint that // () doesn't implement Iterator - std::iter::from_fn(|| todo!()) + // let mut res: Vec = Vec::new(); + let mut res: Vec = Vec::new(); + let mut _iter =_iter; + + while let Some(el) = _iter.next_back() { + res.push(el); + } + + res.into_iter() } diff --git a/exercises/practice/luhn-from/src/lib.rs b/exercises/practice/luhn-from/src/lib.rs index 169f7e6662f..211920eef58 100644 --- a/exercises/practice/luhn-from/src/lib.rs +++ b/exercises/practice/luhn-from/src/lib.rs @@ -1,8 +1,35 @@ -pub struct Luhn; +pub struct Luhn(String); impl Luhn { pub fn is_valid(&self) -> bool { - todo!("Determine if the current Luhn struct contains a valid credit card number."); + + self.0 + .chars() + .filter(|c| !c.is_whitespace()) + .rev() + .try_fold((0, 0), |(i, checksum), c| { + c.to_digit(10) // This (combined with the try_fold) force a return to false if c is not a digit + .map(|d| { + if i % 2 == 1 { + d << 1 + } + else { + d + } + }) + .map(|d| { + if d > 9 { + d - 9 + } + else { + d + } + }) + .map(|d| { + (i+1, checksum+d) + }) + }) + .is_some_and(|(len, checksum)| len > 1 && checksum % 10 == 0) } } @@ -11,8 +38,11 @@ impl Luhn { /// by hand for every other type presented in the test suite, /// but your solution will fail if a new type is presented. /// Perhaps there exists a better solution for this problem? -impl From<&str> for Luhn { - fn from(input: &str) -> Self { - todo!("From the given input '{input}' create a new Luhn struct."); +impl From for Luhn { + // works because String also have a `to_string()` method + fn from(input: T) -> Self { + Self( + input.to_string() + ) } } diff --git a/exercises/practice/luhn-trait/src/lib.rs b/exercises/practice/luhn-trait/src/lib.rs index ccf07bbf8da..9821e31d94a 100644 --- a/exercises/practice/luhn-trait/src/lib.rs +++ b/exercises/practice/luhn-trait/src/lib.rs @@ -7,8 +7,33 @@ pub trait Luhn { /// by hand for every other type presented in the test suite, /// but your solution will fail if a new type is presented. /// Perhaps there exists a better solution for this problem? -impl Luhn for &str { +impl Luhn for T { fn valid_luhn(&self) -> bool { - todo!("Determine if '{self}' is a valid credit card number."); + self + .to_string() + .chars() + .filter(|c| !c.is_whitespace()) + .rev() + .try_fold((0, 0), |(i, checksum), c| { + c.to_digit(10) + .map(|d| { + if i % 2 == 1 { + d << 1 + } + else { + d + } + }) + .map(|d| { + if d > 9 { + d - 9 + } + else { + d + } + }) + .map(|d| (i+1, checksum+d)) + }) + .is_some_and(|(len, checksum)| len > 1 && checksum % 10 == 0) } } diff --git a/exercises/practice/luhn/src/lib.rs b/exercises/practice/luhn/src/lib.rs index 38e363f132f..e0fdc19c7f8 100644 --- a/exercises/practice/luhn/src/lib.rs +++ b/exercises/practice/luhn/src/lib.rs @@ -1,4 +1,28 @@ /// Check a Luhn checksum. pub fn is_valid(code: &str) -> bool { - todo!("Is the Luhn checksum for {code} valid?"); + code.chars().filter(|c| !c.is_whitespace()).rev().try_fold((0, 0), |(i, checksum), c| { + c.to_digit(10).map(|d| { + println!("Doubling every two digits : {} {} {}", i, checksum, d); + if i%2 == 1 { + d *2 + } + else { + d + } + }) + .map(|d| { + println!("Substract 9 if greater than 9 : {} {} {}", i, checksum, d); + if d > 9 { + d - 9 + } + else { + d + } + }) + .map(|d| { + println!("In the accumulator : {} {} {}", i, checksum, d); + (i+1, checksum + d) + }) + }) + .is_some_and(|(len, checksum)| len > 1 && checksum % 10 == 0) } diff --git a/exercises/practice/matching-brackets/src/lib.rs b/exercises/practice/matching-brackets/src/lib.rs index 2adc73149c0..7512943e3d0 100644 --- a/exercises/practice/matching-brackets/src/lib.rs +++ b/exercises/practice/matching-brackets/src/lib.rs @@ -1,3 +1,28 @@ pub fn brackets_are_balanced(string: &str) -> bool { - todo!("Check if the string \"{string}\" contains balanced brackets"); + let mut stack: Vec = Vec::new(); + + for c in string.chars() { + match c { + '[' | '(' | '{' => { + stack.push(c); + }, + ']' => { + if stack.pop() != Some('[') { + return false; + } + }, + ')' => { + if stack.pop() != Some('(') { + return false; + } + }, + '}' => { + if stack.pop() != Some('{') { + return false; + } + }, + _ => continue + } + } + stack.is_empty() } diff --git a/exercises/practice/minesweeper/src/lib.rs b/exercises/practice/minesweeper/src/lib.rs index c19457694ca..3240cbec69a 100644 --- a/exercises/practice/minesweeper/src/lib.rs +++ b/exercises/practice/minesweeper/src/lib.rs @@ -1,5 +1,27 @@ pub fn annotate(minefield: &[&str]) -> Vec { - todo!( - "\nAnnotate each square of the given minefield with the number of mines that surround said square (blank if there are no surrounding mines):\n{minefield:#?}\n" - ); + minefield + .iter() + .enumerate() + .map(|(i, row)| (0..row.len()).map(|j| trans(i, j, minefield)).collect() ) + .collect() } + +fn trans(i: usize, j: usize, minefield: &[&str]) -> char { + if minefield[i].chars().nth(j) == Some('*') { + '*' + } + else { + let count_around = (i.saturating_sub(1)..=i+1) // i+1 can go over range, but is filted out later one with the filter_map + .flat_map(|k| (j.saturating_sub(1)..=j+1).map(move |l| (k, l)) ) + .filter(|&(k, l)| (k, l) != (i, j)) + .filter_map(|(k, l)| minefield.get(k).and_then(|s| s.chars().nth(l))) + .filter(|c| *c == '*') + .count(); + + match count_around { + 0 => ' ', + x @ 0..=9 => char::from_digit(x.try_into().unwrap(), 10).unwrap(), + _ => unreachable!() + } + } +} \ No newline at end of file diff --git a/exercises/practice/nth-prime/src/lib.rs b/exercises/practice/nth-prime/src/lib.rs index 563e947cf67..9b7483ae540 100644 --- a/exercises/practice/nth-prime/src/lib.rs +++ b/exercises/practice/nth-prime/src/lib.rs @@ -1,3 +1,24 @@ pub fn nth(n: u32) -> u32 { - todo!("What is the 0-indexed {n}th prime number?") + let mut primes = Vec::new(); + + primes.push(2); + + let mut i:u32 = 3; + + while primes.len() < (n + 1).try_into().unwrap() { + if i % 2 == 0 { + i += 1; + continue; + } + if (3..=std::cmp::max(3, (i as f64).sqrt() as u32)).step_by(2) + .map(|c| { + i % c == 0 && i != c + }) + .all(|c| !c) { + primes.push(i); + } + i += 1; + } + *primes.last().unwrap() + } diff --git a/exercises/practice/nth-prime/src/main.rs b/exercises/practice/nth-prime/src/main.rs new file mode 100644 index 00000000000..f42e4fc9259 --- /dev/null +++ b/exercises/practice/nth-prime/src/main.rs @@ -0,0 +1,6 @@ +use nth_prime::nth; + +fn main() { + let output = nth(10_000); + println!("{}", output); +} \ No newline at end of file diff --git a/exercises/practice/nucleotide-count/src/lib.rs b/exercises/practice/nucleotide-count/src/lib.rs index c91615c2163..54266b44547 100644 --- a/exercises/practice/nucleotide-count/src/lib.rs +++ b/exercises/practice/nucleotide-count/src/lib.rs @@ -1,9 +1,43 @@ use std::collections::HashMap; pub fn count(nucleotide: char, dna: &str) -> Result { - todo!("How much of nucleotide type '{nucleotide}' is contained inside DNA string '{dna}'?"); + + if !(nucleotide == 'A' || nucleotide == 'C' || nucleotide == 'G' || nucleotide == 'T') { + return Err(nucleotide); + } + + match nucleotide_counts(dna) { + Ok(res_all) => { + if let Some(&res) = res_all.get(&nucleotide) { + Ok(res) + } + else { + Err(nucleotide) + } + }, + Err(c) => { + Err(c) + } + } } pub fn nucleotide_counts(dna: &str) -> Result, char> { - todo!("How much of every nucleotide type is contained inside DNA string '{dna}'?"); + + let non_nucleotides:String = dna.chars().filter(|&c| !(c == 'A' || c == 'C' || c == 'G' || c == 'T')).collect(); + + if !non_nucleotides.is_empty() { + return Err(non_nucleotides.chars().last().unwrap()) + } + + let mut res: HashMap = HashMap::new(); + + for c in ['A', 'C', 'G', 'T'] { + res.insert(c, 0); + } + + for c in dna.chars() { + res.entry(c).and_modify(|counter| *counter += 1); + } + + Ok(res) } diff --git a/exercises/practice/paasio/src/lib.rs b/exercises/practice/paasio/src/lib.rs index 06666d1725c..116b3c61621 100644 --- a/exercises/practice/paasio/src/lib.rs +++ b/exercises/practice/paasio/src/lib.rs @@ -3,64 +3,83 @@ use std::io::{Read, Result, Write}; // the PhantomData instances in this file are just to stop compiler complaints // about missing generics; feel free to remove them -pub struct ReadStats(::std::marker::PhantomData); +pub struct ReadStats{ + wrapped: R, + reads: usize, + bytes_through: usize +} impl ReadStats { - // _wrapped is ignored because R is not bounded on Debug or Display and therefore - // can't be passed through format!(). For actual implementation you will likely - // wish to remove the leading underscore so the variable is not ignored. - pub fn new(_wrapped: R) -> ReadStats { - todo!() + pub fn new(wrapped: R) -> ReadStats { + Self { + wrapped, + reads: 0, + bytes_through: 0 + } } pub fn get_ref(&self) -> &R { - todo!() + &self.wrapped } pub fn bytes_through(&self) -> usize { - todo!() + self.bytes_through } pub fn reads(&self) -> usize { - todo!() + self.reads } } impl Read for ReadStats { fn read(&mut self, buf: &mut [u8]) -> Result { - todo!("Collect statistics about this call reading {buf:?}") + self.reads += 1; + self.wrapped.read(buf).inspect(|qty| { + self.bytes_through += qty; + }) } } -pub struct WriteStats(::std::marker::PhantomData); +pub struct WriteStats{ + wrapped: W, + bytes_through: usize, + writes: usize +} impl WriteStats { // _wrapped is ignored because W is not bounded on Debug or Display and therefore // can't be passed through format!(). For actual implementation you will likely // wish to remove the leading underscore so the variable is not ignored. - pub fn new(_wrapped: W) -> WriteStats { - todo!() + pub fn new(wrapped: W) -> WriteStats { + Self { + wrapped, + bytes_through: 0, + writes: 0 + } } pub fn get_ref(&self) -> &W { - todo!() + &self.wrapped } pub fn bytes_through(&self) -> usize { - todo!() + self.bytes_through } pub fn writes(&self) -> usize { - todo!() + self.writes } } impl Write for WriteStats { fn write(&mut self, buf: &[u8]) -> Result { - todo!("Collect statistics about this call writing {buf:?}") + self.writes += 1; + self.wrapped.write(buf).inspect(|qty| { + self.bytes_through += qty; + }) } fn flush(&mut self) -> Result<()> { - todo!() + self.wrapped.flush() } } diff --git a/exercises/practice/palindrome-products/Cargo.toml b/exercises/practice/palindrome-products/Cargo.toml index 6629a39339f..a13a4906dd4 100644 --- a/exercises/practice/palindrome-products/Cargo.toml +++ b/exercises/practice/palindrome-products/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +itertools = "0.12" \ No newline at end of file diff --git a/exercises/practice/palindrome-products/src/lib.rs b/exercises/practice/palindrome-products/src/lib.rs index 5b46d9c21b4..b7b6849c625 100644 --- a/exercises/practice/palindrome-products/src/lib.rs +++ b/exercises/practice/palindrome-products/src/lib.rs @@ -1,22 +1,67 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; +use itertools::Itertools; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Palindrome { - // TODO + value: u64, + factors: HashSet<(u64, u64)> } impl Palindrome { + pub fn new(value: u64, factors: HashSet<(u64, u64)>) -> Self { + Self { + value, + factors + } + } + pub fn value(&self) -> u64 { - todo!("return the value of the palindrome") + self.value } pub fn into_factors(self) -> HashSet<(u64, u64)> { - todo!("return the set of factors of the palindrome") + self.factors } } +// Can be done with just two palindromes (palindrome_min and palindrome_max) that can +// be updated. Rather than with a full HashMap> pub fn palindrome_products(min: u64, max: u64) -> Option<(Palindrome, Palindrome)> { - todo!( - "returns the minimum palindrome and maximum palindrome of the products of two factors in the range {min} to {max}" - ); + let mut palindromes: HashMap> = HashMap::new(); + + let palindrome_factors:Vec<(u64, u64)> = (min..=max) + .combinations_with_replacement(2) + .filter(|pair| is_palindrome(pair[0] * pair[1])) + .map(|pair| (pair[0], pair[1])).collect(); + + println!("{:?}",palindrome_factors); + + if palindrome_factors.is_empty() { + return None; + } + + for pair in palindrome_factors { + palindromes.entry(pair.0*pair.1).or_insert(HashSet::new()).insert(pair); + } + + println!("{:?}",palindromes); + + let min_key = palindromes.keys().min().unwrap(); + let max_key = palindromes.keys().max().unwrap(); + + Some((Palindrome::new(*min_key, palindromes.get(min_key).unwrap().clone()), + Palindrome::new(*max_key, palindromes.get(max_key).unwrap().clone()))) + +} + +fn is_palindrome(n:u64) -> bool { + let mut input = n; + let mut reversed = 0; + + while input > 0 { + reversed = reversed*10 + (input%10); + input /= 10; + } + + reversed == n } diff --git a/exercises/practice/pangram/src/lib.rs b/exercises/practice/pangram/src/lib.rs index 685588e2f70..e8bebdcbea0 100644 --- a/exercises/practice/pangram/src/lib.rs +++ b/exercises/practice/pangram/src/lib.rs @@ -1,4 +1,16 @@ +use std::collections::HashSet; + /// Determine whether a sentence is a pangram. pub fn is_pangram(sentence: &str) -> bool { - todo!("Is {sentence} a pangram?"); + let mut set: HashSet = HashSet::new(); + + sentence + .chars() + .filter(|c| c.is_ascii_alphabetic()) + .map(|c| c.to_ascii_lowercase()) + .for_each(|c| { + set.insert(c); + }); + + set.len() == 26 } diff --git a/exercises/practice/pascals-triangle/src/lib.rs b/exercises/practice/pascals-triangle/src/lib.rs index 7e7bf3bbf34..761e5f43963 100644 --- a/exercises/practice/pascals-triangle/src/lib.rs +++ b/exercises/practice/pascals-triangle/src/lib.rs @@ -1,11 +1,41 @@ -pub struct PascalsTriangle; +#[derive(Default)] +pub struct PascalsTriangle { + row_count: u32, + rows: Vec> +} impl PascalsTriangle { pub fn new(row_count: u32) -> Self { - todo!("create Pascal's triangle with {row_count} rows"); + let mut res = + Self { + row_count, + ..Default::default() + }; + + for i in 1..=res.row_count as usize { + println!("i: {}", i); + + let mut row:Vec = Vec::new(); + row.push(1); + + for j in 1..i-1 { + row.push(res.rows[i-2][j] + res.rows[i-2][j-1]); + } + + if i > 1 { + row.push(1); + } + + res.rows.push(row.clone()); + + println!("{:?}", row); + + } + + res } pub fn rows(&self) -> Vec> { - todo!(); + self.rows.clone() } } diff --git a/exercises/practice/perfect-numbers/src/lib.rs b/exercises/practice/perfect-numbers/src/lib.rs index b2257e1b0ff..bc7f3fb989c 100644 --- a/exercises/practice/perfect-numbers/src/lib.rs +++ b/exercises/practice/perfect-numbers/src/lib.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + #[derive(Debug, PartialEq, Eq)] pub enum Classification { Abundant, @@ -6,5 +8,26 @@ pub enum Classification { } pub fn classify(num: u64) -> Option { - todo!("classify {num}"); + if num < 1 { + return None; + } + + let mut aliquot_sum = 0; + for i in 1..num { + if num % i == 0 { + aliquot_sum += i; + } + } + + match aliquot_sum.cmp(&num) { + Ordering::Less => { + Some(Classification::Deficient) + }, + Ordering::Greater => { + Some(Classification::Abundant) + }, + Ordering::Equal => { + Some(Classification::Perfect) + } + } } diff --git a/exercises/practice/phone-number/src/lib.rs b/exercises/practice/phone-number/src/lib.rs index af53e11c8f4..2152e60501d 100644 --- a/exercises/practice/phone-number/src/lib.rs +++ b/exercises/practice/phone-number/src/lib.rs @@ -1,5 +1,35 @@ pub fn number(user_number: &str) -> Option { - todo!( - "Given the number entered by user '{user_number}', convert it into SMS-friendly format. If the entered number is not a valid NANP number, return None." - ); + let mut digits: Vec = user_number + .chars() + .filter(|c| c.is_numeric()) + .map(|c| c.to_digit(10).unwrap() as u8) + .collect(); + + // Handle potential country code + if digits.first() == Some(&1) { + digits.remove(0); + } + + // If not 10 digits after removing potential country code, then invalid phone number + if digits.len() != 10 { + return None; + } + + // Check N for area code + match digits.first() { + Some(2..=9) => (), + _ => { + return None; + } + } + + // Check N for local code + match digits.get(3) { + Some(2..=9) => (), + _ => { + return None; + } + } + + Some(digits.into_iter().map(|c| c.to_string()).collect()) } diff --git a/exercises/practice/pig-latin/Cargo.toml b/exercises/practice/pig-latin/Cargo.toml index 4dc02d93f3e..709a859dfbb 100644 --- a/exercises/practice/pig-latin/Cargo.toml +++ b/exercises/practice/pig-latin/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +regex = "1" diff --git a/exercises/practice/pig-latin/src/lib.rs b/exercises/practice/pig-latin/src/lib.rs index f395af0b8a4..000d7117d41 100644 --- a/exercises/practice/pig-latin/src/lib.rs +++ b/exercises/practice/pig-latin/src/lib.rs @@ -1,3 +1,107 @@ +use regex::Regex; + +fn starts_with_vowel_or_special(s: &str) -> String { + let re = Regex::new(r"^(?:[aeiou]|xr|yt)").unwrap(); + if re.is_match(s) { + format!("{}{}", s, "ay") + } + else { + s.to_string() + } +} + +fn move_leading_consonants_to_end(s: &str) -> String { + // This regex captures: + // - group 1: leading consonants (anything not starting with a vowel) + // - group 2: the rest of the string + let re = Regex::new(r"^([^aeiou]+)(.*)").unwrap(); + + if let Some(caps) = re.captures(s) { + let consonants = caps.get(1).unwrap().as_str(); + let rest = caps.get(2).unwrap().as_str(); + format!("{}{}{}", rest, consonants, "ay") + } + else { + s.to_string() + } +} + +fn move_leading_consonants_and_qu_to_end(s: &str) -> String { + // This regex captures: + // - group 1: leading consonants (anything not starting with a vowel). * means it is zero or more + // - group 2: qu + // - group 3: the rest of the string + let re = Regex::new(r"^([^aeiou]*)(qu)(.*)").unwrap(); + + if let Some(caps) = re.captures(s) { + let leading_consonants = caps.get(1).unwrap().as_str(); + let qu = caps.get(2).unwrap().as_str(); + let rest = caps.get(3).unwrap().as_str(); + println!("{} {} {}", leading_consonants, qu, rest); + format!("{}{}{}{}", rest, leading_consonants, qu, "ay") + } + else { + s.to_string() + } + +} + +fn move_leading_consonants_and_y_to_end(s: &str) -> String { + // This regex captures: + // - group 1: leading consonants (anything not starting with a vowel). + means it has to be at least one + // - group 2: y + // - group 3: the rest of the string + let re = Regex::new(r"^([^aeiou]+)(y)(.*)").unwrap(); + + if let Some(caps) = re.captures(s) { + let leading_consonants = caps.get(1).unwrap().as_str(); + let y = caps.get(2).unwrap().as_str(); + let rest = caps.get(3).unwrap().as_str(); + println!("{} {} {}", leading_consonants, y, rest); + format!("{}{}{}{}", y, rest, leading_consonants, "ay") + } + else { + s.to_string() + } + +} + pub fn translate(input: &str) -> String { - todo!("Using the Pig Latin text transformation rules, convert the given input '{input}'"); + let mut res = String::new(); + // let mut res_inner = String::new(); + + for word in input.split_whitespace() { + + let mut res_inner = starts_with_vowel_or_special(word); + if res_inner != word { + res_inner.push(' '); + res.push_str(&res_inner); + continue; + } + + res_inner = move_leading_consonants_and_qu_to_end(word); + println!("{}", res); + if res_inner != word { + res_inner.push(' '); + res.push_str(&res_inner); + continue; + } + + res_inner = move_leading_consonants_and_y_to_end(word); + println!("{}", res); + if res_inner != word { + res_inner.push(' '); + res.push_str(&res_inner); + continue; + } + + res_inner = move_leading_consonants_to_end(word); + println!("{}", res); + if res_inner != word { + res_inner.push(' '); + res.push_str(&res_inner); + continue; + } + } + res.trim().to_string() } diff --git a/exercises/practice/prime-factors/src/lib.rs b/exercises/practice/prime-factors/src/lib.rs index 30d06a2fc88..ed56c0cdc73 100644 --- a/exercises/practice/prime-factors/src/lib.rs +++ b/exercises/practice/prime-factors/src/lib.rs @@ -1,3 +1,76 @@ pub fn factors(n: u64) -> Vec { - todo!("This should calculate the prime factors of {n}") + let mut res: Vec = Vec::new(); + + if n < 2 { + return res; + } + + let mut start = 2; + let mut n: u64 = n; + + 'begin: loop { + println!("-------- In Loop --------"); + println!("n: {}, start: {}", n, start); + for i in start..(n/2+1) { + println!("n: {}, i: {}, n % i: {}", n, i, n % i); + if n % i == 0 { + res.push(i); + n /= i; + start = i; + continue 'begin; + } + } + res.push(n); + break 'begin; + } + res } + +// pub fn factors(n: u64) -> Vec { +// let mut res: Vec = Vec::new(); + +// if n < 2 { +// return res; +// } + +// let mut n = n; +// let mut i:u64 = 0; +// let mut prime_factor = nth(i); + +// while n > 1{ +// if n % prime_factor == 0 { +// res.push(prime_factor); +// n /= prime_factor; +// } +// else { +// i+=1; +// prime_factor = nth(i); +// } +// } +// res +// } + +// pub fn nth(n: u64) -> u64 { +// let mut primes = Vec::new(); + +// primes.push(2); + +// let mut i:u64 = 3; + +// while primes.len() < (n + 1).try_into().unwrap() { +// if i % 2 == 0 { +// i += 1; +// continue; +// } +// if (3..=std::cmp::max(3, (i as f64).sqrt() as u64)).step_by(2) +// .map(|c| { +// i % c == 0 && i != c +// }) +// .all(|c| !c) { +// primes.push(i); +// } +// i += 1; +// } +// *primes.last().unwrap() + +// } \ No newline at end of file diff --git a/exercises/practice/protein-translation/src/lib.rs b/exercises/practice/protein-translation/src/lib.rs index b5ba6a902ea..218825267b8 100644 --- a/exercises/practice/protein-translation/src/lib.rs +++ b/exercises/practice/protein-translation/src/lib.rs @@ -1,5 +1,31 @@ pub fn translate(rna: &str) -> Option> { - todo!( - "Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid" - ); + // Let's suppose the string is ASCII-only + + let bytes_len = rna.bytes().len(); + + let codons = (0..bytes_len) + .step_by(3) + .map(|i| &rna[i..(i+3).min(bytes_len)]); + + let mut res: Vec<&str> = Vec::new(); + + for codon in codons { + match codon { + "AUG" => res.push("Methionine"), + "UUU" | "UUC" => res.push("Phenylalanine"), + "UUA" | "UUG" => res.push("Leucine"), + "UCU" | "UCC" | "UCA" | "UCG" => res.push("Serine"), + "UAU" | "UAC" => res.push("Tyrosine"), + "UGU" | "UGC" => res.push("Cysteine"), + "UGG" => res.push("Tryptophan"), + "UAA" | "UAG" | "UGA" => { + break; + }, + _ => { + return None; + } + } + } + + Some(res) } diff --git a/exercises/practice/proverb/src/lib.rs b/exercises/practice/proverb/src/lib.rs index 56705813ebc..3cc880fc83d 100644 --- a/exercises/practice/proverb/src/lib.rs +++ b/exercises/practice/proverb/src/lib.rs @@ -1,3 +1,13 @@ +use std::iter::once; + pub fn build_proverb(list: &[&str]) -> String { - todo!("build a proverb from this list of items: {list:?}") + if list.is_empty() { + return String::new(); + } + + list.windows(2) + .map(|words| format!("For want of a {} the {} was lost.\n", words[0], words[1])) + .chain(once(format!("And all for the want of a {}.", list[0]))) + .collect() + } diff --git a/exercises/practice/queen-attack/src/lib.rs b/exercises/practice/queen-attack/src/lib.rs index 2c6840034b1..c2cb3ecde9b 100644 --- a/exercises/practice/queen-attack/src/lib.rs +++ b/exercises/practice/queen-attack/src/lib.rs @@ -1,23 +1,70 @@ +use std::cmp::{max, min}; + #[derive(Debug)] -pub struct ChessPosition; +pub struct ChessPosition { + rank: i32, + file: i32 +} #[derive(Debug)] -pub struct Queen; +pub struct Queen { + position: ChessPosition +} impl ChessPosition { pub fn new(rank: i32, file: i32) -> Option { - todo!( - "Construct a ChessPosition struct, given the following rank, file: ({rank}, {file}). If the position is invalid return None." - ); + if !(0..8).contains(&rank) || !(0..8).contains(&file) { + return None; + } + Some(Self { + rank, + file}) } } impl Queen { pub fn new(position: ChessPosition) -> Self { - todo!("Given the chess position {position:?}, construct a Queen struct."); + Self { + position + } } pub fn can_attack(&self, other: &Queen) -> bool { - todo!("Determine if this Queen can attack the other Queen {other:?}"); + if self.position.rank == other.position.rank { + return true; + } + if self.position.file == other.position.file { + return true; + } + + // lower right + for i in 1..=(7-max(self.position.rank, self.position.file)) { + if other.position.rank == self.position.rank + i && other.position.file == self.position.file + i { + return true; + } + } + + // upper left + for i in 1..=min(self.position.rank, self.position.file) { + if other.position.rank == self.position.rank - i && other.position.file == self.position.file - i { + return true; + } + } + + // upper right + for i in 1..=min(self.position.rank, 7 - self.position.file) { + if other.position.rank == self.position.rank - i && other.position.file == self.position.file + i { + return true; + } + } + + // lower left + for i in 1..=min(7 - self.position.rank, self.position.file) { + if other.position.rank == self.position.rank + i && other.position.file == self.position.file - i { + return true; + } + } + + false } } diff --git a/exercises/practice/rail-fence-cipher/src/lib.rs b/exercises/practice/rail-fence-cipher/src/lib.rs index 4b72261ceec..3468f3d0832 100644 --- a/exercises/practice/rail-fence-cipher/src/lib.rs +++ b/exercises/practice/rail-fence-cipher/src/lib.rs @@ -1,15 +1,119 @@ -pub struct RailFence; +pub struct RailFence{ + nb_rails: usize, +} impl RailFence { pub fn new(rails: u32) -> RailFence { - todo!("Construct a new fence with {rails} rails") - } + Self { + nb_rails: rails as usize, + } + } pub fn encode(&self, text: &str) -> String { - todo!("Encode this text: {text}") + // Create rails here (since &self is not mutable) + let mut rails = Vec::new(); + (0..self.nb_rails).for_each(|_| rails.push(Vec::new())); + + // Utils for parse rails in the proper order + let mut i = 0; + let mut direction = 1; + + let chars = text.chars(); + // As long as there are some char left, distribute them into rails + // while let Some(c) = chars.next() { + for c in chars { + if i == self.nb_rails - 1 { + direction = -1; + } + else if i == 0 { + direction = 1; + } + + rails.get_mut(i).unwrap().push(c); + + // Update i + i = (i as i32 + direction) as usize; + } + + // Flatten everything + rails + .iter() + .flatten() + .collect() + } pub fn decode(&self, cipher: &str) -> String { - todo!("Decode this ciphertext: {cipher}") + + let nb_chars = cipher.chars().count(); + let nb_cycles = nb_chars / (2*self.nb_rails-2); + let nb_left_chars = nb_chars % (2*self.nb_rails-2); + + // For each cycle : 1 letter in the first rail, 1 letter in the last rail, 2 letters in other rails + let mut rails_sizes: Vec = (0..self.nb_rails).map(|i| { + if i == 0 || i == self.nb_rails - 1 { + nb_cycles + } + else { + 2*nb_cycles + } + }) + .collect(); + + println!("nb_chars {nb_chars}, nb_cycles {nb_cycles}, nb_left_chars {nb_left_chars}, {rails_sizes:?}"); + + // distributed what is left + let mut i = 0; + let mut direction = 1; + (0..nb_left_chars).for_each(|_| { + *rails_sizes.get_mut(i).unwrap() += 1; + + if i == self.nb_rails - 1 { + direction = -1; + } + else if i == 0 { + direction = 1; + } + + i = (i as i32 + direction) as usize; + }); + + println!("{rails_sizes:?}"); + + // Create rails + let mut rails: Vec> = Vec::new(); + (0..self.nb_rails).for_each(|_| rails.push(Vec::new())); + + rails_sizes + .iter() + .enumerate() + .fold(0, |char_offset, (rail_index,rail_len)| { + let rail = rails.get_mut(rail_index).unwrap(); + *rail = cipher.chars().skip(char_offset).take(*rail_len).collect::>(); + char_offset + *rail_len + }); + + println!("{rails:?}"); + + // Build output from rails + let mut res: Vec = Vec::new(); + + i = 0; + direction = 1; + (0..nb_chars).for_each(|_| { + // Take the first char of the current rail + res.push(rails.get_mut(i).unwrap().remove(0)); + + if i == self.nb_rails - 1 { + direction = -1; + } + else if i == 0 { + direction = 1; + } + i = (i as i32 + direction) as usize; + }); + + res.iter().collect() + } } diff --git a/exercises/practice/raindrops/src/lib.rs b/exercises/practice/raindrops/src/lib.rs index c16e39c1864..18eed539186 100644 --- a/exercises/practice/raindrops/src/lib.rs +++ b/exercises/practice/raindrops/src/lib.rs @@ -1,3 +1,20 @@ pub fn raindrops(n: u32) -> String { - todo!("what sound does Raindrop #{n} make?") + let mut res = String::new(); + + if n % 3 == 0 { + res.push_str("Pling"); + } + if n % 5 == 0 { + res.push_str("Plang"); + } + if n % 7 == 0 { + res.push_str("Plong"); + } + + if res.is_empty() { + format!("{}", n) + } + else { + res + } } diff --git a/exercises/practice/reverse-string/src/lib.rs b/exercises/practice/reverse-string/src/lib.rs index 01e0d4dc8e6..5933e404468 100644 --- a/exercises/practice/reverse-string/src/lib.rs +++ b/exercises/practice/reverse-string/src/lib.rs @@ -1,3 +1,3 @@ pub fn reverse(input: &str) -> String { - todo!("Write a function to reverse {input}"); + input.chars().rev().collect() } diff --git a/exercises/practice/reverse-string/tests/reverse_string.rs b/exercises/practice/reverse-string/tests/reverse_string.rs index 9752045735d..d05ca9584cb 100644 --- a/exercises/practice/reverse-string/tests/reverse_string.rs +++ b/exercises/practice/reverse-string/tests/reverse_string.rs @@ -9,7 +9,6 @@ fn an_empty_string() { } #[test] -#[ignore] fn a_word() { let input = "robot"; let output = reverse(input); @@ -18,7 +17,6 @@ fn a_word() { } #[test] -#[ignore] fn a_capitalized_word() { let input = "Ramen"; let output = reverse(input); @@ -27,7 +25,6 @@ fn a_capitalized_word() { } #[test] -#[ignore] fn a_sentence_with_punctuation() { let input = "I'm hungry!"; let output = reverse(input); @@ -36,7 +33,6 @@ fn a_sentence_with_punctuation() { } #[test] -#[ignore] fn a_palindrome() { let input = "racecar"; let output = reverse(input); @@ -45,7 +41,6 @@ fn a_palindrome() { } #[test] -#[ignore] fn an_even_sized_word() { let input = "drawer"; let output = reverse(input); @@ -54,7 +49,6 @@ fn an_even_sized_word() { } #[test] -#[ignore] fn wide_characters() { let input = "子猫"; let output = reverse(input); @@ -63,7 +57,6 @@ fn wide_characters() { } #[test] -#[ignore] #[cfg(feature = "grapheme")] fn grapheme_cluster_with_pre_combined_form() { let input = "Würstchenstand"; @@ -73,7 +66,6 @@ fn grapheme_cluster_with_pre_combined_form() { } #[test] -#[ignore] #[cfg(feature = "grapheme")] fn grapheme_clusters() { let input = "ผู้เขียนโปรแกรม"; diff --git a/exercises/practice/rna-transcription/Cargo.toml b/exercises/practice/rna-transcription/Cargo.toml index acb68cde0cf..628db0d6ab1 100644 --- a/exercises/practice/rna-transcription/Cargo.toml +++ b/exercises/practice/rna-transcription/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +once_cell = "1.18" \ No newline at end of file diff --git a/exercises/practice/rna-transcription/src/lib.rs b/exercises/practice/rna-transcription/src/lib.rs index c235cfad396..6b59133e53a 100644 --- a/exercises/practice/rna-transcription/src/lib.rs +++ b/exercises/practice/rna-transcription/src/lib.rs @@ -1,25 +1,65 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; + +static DNA_TO_RNA_MAP: Lazy> = Lazy::new(|| { + let mut m: HashMap = HashMap::new(); + m.insert('G', 'C'); + m.insert('C', 'G'); + m.insert('T', 'A'); + m.insert('A', 'U'); + m +}); + #[derive(Debug, PartialEq, Eq)] -pub struct Dna; +pub struct Dna { + nucleotides: String +} #[derive(Debug, PartialEq, Eq)] -pub struct Rna; +pub struct Rna { + nucleotides : String +} + impl Dna { pub fn new(dna: &str) -> Result { - todo!( - "Construct new Dna from '{dna}' string. If string contains invalid nucleotides return index of first invalid nucleotide" - ); + let keys: Vec = DNA_TO_RNA_MAP.keys().cloned().collect(); + + match dna.char_indices() + .find(|(_i, c)| !keys.contains(c)) + .map(|(i, _c)| i) { + Some(i) => Err(i), + None => Ok(Self{ + nucleotides: dna.to_string() + }) + } } pub fn into_rna(self) -> Rna { - todo!("Transform Dna {self:?} into corresponding Rna"); + let rna_nucleotides: String = self.nucleotides + .chars() + .map(|c| DNA_TO_RNA_MAP.get(&c).unwrap()) + .collect(); + + Rna { + nucleotides: rna_nucleotides + } } } impl Rna { pub fn new(rna: &str) -> Result { - todo!( - "Construct new Rna from '{rna}' string. If string contains invalid nucleotides return index of first invalid nucleotide" - ); + let values: Vec = DNA_TO_RNA_MAP.values().cloned().collect(); + + match rna.char_indices() + .find(|(_i, c)| !values.contains(c)) + .map(|(i, _c)| i) { + Some(i) => Err(i), + None => Ok( + Self{ + nucleotides: rna.to_string() + } + ) + } } } diff --git a/exercises/practice/robot-name/Cargo.toml b/exercises/practice/robot-name/Cargo.toml index d584df2f038..a25223c1c0a 100644 --- a/exercises/practice/robot-name/Cargo.toml +++ b/exercises/practice/robot-name/Cargo.toml @@ -7,6 +7,8 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +once_cell = "1.18" +rand = "0.8" [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/robot-name/src/lib.rs b/exercises/practice/robot-name/src/lib.rs index 37bf158ed32..a0058594c19 100644 --- a/exercises/practice/robot-name/src/lib.rs +++ b/exercises/practice/robot-name/src/lib.rs @@ -1,15 +1,56 @@ -pub struct Robot; +use once_cell::sync::Lazy; +use rand::{thread_rng, Rng}; +use std::collections::HashSet; +use std::sync::Mutex; + +pub struct Robot { + name: String +} + +static ROBOT_NAMES: Lazy>> = Lazy::new(|| { + Mutex::new(HashSet::new()) +}); + +fn generate_robot_name() -> String { + let mut rng = thread_rng(); + + let letters: String = (0..2) + .map(|_| rng.gen_range(b'A'..=b'Z') as char) + .collect(); + + let digits: String = (0..3) + .map(|_| rng.gen_range(0..=9).to_string()) + .collect(); + + format!("{letters}{digits}") +} impl Robot { pub fn new() -> Self { - todo!("Construct a new Robot struct."); + loop { + let name = generate_robot_name(); + if ROBOT_NAMES.lock().unwrap().insert(name.clone()) { + return Self {name} + } + } } pub fn name(&self) -> &str { - todo!("Return the reference to the robot's name."); + &self.name } pub fn reset_name(&mut self) { - todo!("Assign a new unique name to the robot."); + // The generation of the new name, the removing of the old name and the + // affectation of the new one are perform during the lock + let mut names = ROBOT_NAMES.lock().unwrap(); + + loop { + let name = generate_robot_name(); + if names.insert(name.clone()) { + names.remove(self.name()); + self.name = name; + break; + } + } } } diff --git a/exercises/practice/robot-simulator/src/lib.rs b/exercises/practice/robot-simulator/src/lib.rs index ff3fab8caad..46da8436cf5 100644 --- a/exercises/practice/robot-simulator/src/lib.rs +++ b/exercises/practice/robot-simulator/src/lib.rs @@ -9,38 +9,83 @@ pub enum Direction { West, } -pub struct Robot; +pub struct Robot { + x: i32, + y: i32, + direction: Direction +} impl Robot { pub fn new(x: i32, y: i32, d: Direction) -> Self { - todo!("Create a robot at (x, y) ({x}, {y}) facing {d:?}") + Self { + x, + y, + direction: d + } } #[must_use] pub fn turn_right(self) -> Self { - todo!() + Self { + x: self.x, + y: self.y, + direction: match self.direction { + Direction::East => Direction::South, + Direction::North => Direction::East, + Direction::South => Direction::West, + Direction::West => Direction::North + } + } } #[must_use] pub fn turn_left(self) -> Self { - todo!() + Self { + x: self.x, + y: self.y, + direction: match self.direction { + Direction::East => Direction::North, + Direction::North => Direction::West, + Direction::South => Direction::East, + Direction::West => Direction::South + } + } } #[must_use] pub fn advance(self) -> Self { - todo!() + let (newx, newy) = match self.direction { + Direction::East => (self.x + 1, self.y), + Direction::North => (self.x, self.y + 1), + Direction::South => (self.x, self.y - 1), + Direction::West => (self.x - 1, self.y) + }; + Self { + x: newx, + y: newy, + direction: self.direction + } } #[must_use] pub fn instructions(self, instructions: &str) -> Self { - todo!("Follow the given sequence of instructions: {instructions}") + instructions + .chars() + .fold(self, |robot, instruction| { + match instruction { + 'A' => robot.advance(), + 'R' => robot.turn_right(), + 'L' => robot.turn_left(), + _ => robot + } + }) } pub fn position(&self) -> (i32, i32) { - todo!() + (self.x, self.y) } pub fn direction(&self) -> &Direction { - todo!() + &self.direction } } diff --git a/exercises/practice/roman-numerals/src/lib.rs b/exercises/practice/roman-numerals/src/lib.rs index 97ae5cf73c1..9f783207488 100644 --- a/exercises/practice/roman-numerals/src/lib.rs +++ b/exercises/practice/roman-numerals/src/lib.rs @@ -1,15 +1,40 @@ -use std::fmt::{Display, Formatter, Result}; +use std::{fmt::{Display, Formatter, Result}}; -pub struct Roman; +pub struct Roman(u32); impl Display for Roman { fn fmt(&self, _f: &mut Formatter<'_>) -> Result { - todo!("Return a roman-numeral string representation of the Roman object"); + let roman_map = [(1000, "M"), + (900, "CM"), + (500, "D"), + (400, "CD"), + (100, "C"), + (90, "XC"), + (50, "L"), + (40, "XL"), + (10, "X"), + (9, "IX"), + (5, "V"), + (4, "IV"), + (1, "I")]; + + let mut res = String::new(); + let mut num = self.0; + + for &(divisor, roman) in roman_map.iter() { + while num >= divisor { + res.push_str(roman); + num -= divisor; + } + } + + write!(_f, "{res}") + } } impl From for Roman { fn from(num: u32) -> Self { - todo!("Construct a Roman object from the '{num}' number"); + Self(num) } } diff --git a/exercises/practice/rotational-cipher/Cargo.toml b/exercises/practice/rotational-cipher/Cargo.toml index 61e6d3c4194..4a5e794f329 100644 --- a/exercises/practice/rotational-cipher/Cargo.toml +++ b/exercises/practice/rotational-cipher/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +once_cell = "1.18" diff --git a/exercises/practice/rotational-cipher/src/lib.rs b/exercises/practice/rotational-cipher/src/lib.rs index a1d0b53e56f..fca354ce490 100644 --- a/exercises/practice/rotational-cipher/src/lib.rs +++ b/exercises/practice/rotational-cipher/src/lib.rs @@ -1,5 +1,35 @@ +use once_cell::sync::Lazy; + +static ALPHABET: Lazy> = Lazy::new(|| { + ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z'].into_iter().collect() +}); + pub fn rotate(input: &str, key: u8) -> String { - todo!( - "How would input text '{input}' transform when every letter is shifted using key '{key}'?" - ); + let mut rotated_alphabet: Vec = ALPHABET.clone(); + rotated_alphabet.rotate_left(key as usize); + + input + .chars() + .map(|c| { + if c.is_alphabetic() { + rotated_alphabet + .get(ALPHABET.iter().position(|&cc| cc == c.to_ascii_lowercase()).unwrap()) + .map(|rot_c| { + if c.is_uppercase() { + rot_c.to_ascii_uppercase() + } + else { + *rot_c + } + }) + .unwrap() + } + else { + c + } + }) + .collect() } diff --git a/exercises/practice/run-length-encoding/src/lib.rs b/exercises/practice/run-length-encoding/src/lib.rs index 9b830aec979..880a4da79eb 100644 --- a/exercises/practice/run-length-encoding/src/lib.rs +++ b/exercises/practice/run-length-encoding/src/lib.rs @@ -1,7 +1,52 @@ pub fn encode(source: &str) -> String { - todo!("Return the run-length encoding of {source}."); + let mut chars = source.chars().peekable(); + let mut c_count:i32; + let mut res = String::new(); + + while let Some(c) = chars.next() { + c_count = 1; + while let Some(&next_c) = chars.peek() { + if next_c != c { + break; + } + c_count+=1; + chars.next(); + } + if c_count > 1 { + res.push_str(&c_count.to_string()); + } + res.push(c); + } + + res + } pub fn decode(source: &str) -> String { - todo!("Return the run-length decoding of {source}."); + let mut chars = source.chars().peekable(); + let mut res = String::new(); + let mut c_count = 0; + + while let Some(c) = chars.next() { + if c.is_ascii_digit() { + c_count = c.to_digit(10).unwrap(); + while let Some(next_c) = chars.peek() { + if next_c.is_ascii_digit() { + c_count = c_count*10 + next_c.to_digit(10).unwrap(); + chars.next(); + } + else { + break; + } + } + } + else { + if c_count == 0 { + c_count = 1; + } + res.push_str(&c.to_string().repeat(c_count as usize)); + c_count = 0; + } + } + res } diff --git a/exercises/practice/saddle-points/src/lib.rs b/exercises/practice/saddle-points/src/lib.rs index 5e3a95bcd2e..525a2e8cbc2 100644 --- a/exercises/practice/saddle-points/src/lib.rs +++ b/exercises/practice/saddle-points/src/lib.rs @@ -1,3 +1,91 @@ +use std::collections::{HashMap, HashSet}; + pub fn find_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { - todo!("find the saddle points of the following matrix: {input:?}") + let row_maxs_locations = row_max_locations(input); + println!("{:?}", row_maxs_locations); + + // Only search mins for columns with a row max. + let cols_to_consider: HashSet = row_maxs_locations + .iter() + .map(|(_, second)| *second) + .collect(); + println!("{:?}", cols_to_consider); + + // Compute minima for relevant columns + let cols_min= column_minima(input, &cols_to_consider); + println!("{:?}", cols_min); + + // Only return row max corresponding column mins + row_maxs_locations + .iter() + .filter_map(|(i, j)| { + if let Some(val) = input + .get(*i) + .and_then(|row| row.get(*j)) { + if val == cols_min.get(j).unwrap() { + Some((*i, *j)) + } + else { + None + } + } + else { + None + } + }) + .collect() + } + + +pub fn row_max_locations(matrix: &[Vec]) -> Vec<(usize, usize)> { + matrix + .iter() + .enumerate() + .flat_map(|(i, row)| { + if let Some(row_max) = row.iter().copied().max() { + row + .iter() + .enumerate() + .filter_map(|(j, &val)| { + if val == row_max { + Some((i, j)) + } + else { + None + } + }) + .collect() // each row generate a [(a1, b1), (a2, b2)] + } + else { + vec![] + } + }) + // .flatten() + .collect() +} + +pub fn column_minima(matrix: &[Vec], cols: &HashSet) -> HashMap { + let mut minima: HashMap = HashMap::new(); + + if matrix.is_empty() { + return minima; + } + + for &i in cols { + minima.insert(i, u64::MAX) ; + + for row in matrix { + let val = row.get(i).unwrap(); + + minima + .entry(i) + .and_modify(|v| + if *v > *val { + *v = *val; + } + ); + } + } + minima +} \ No newline at end of file diff --git a/exercises/practice/saddle-points/src/main.rs b/exercises/practice/saddle-points/src/main.rs new file mode 100644 index 00000000000..faef5f2b966 --- /dev/null +++ b/exercises/practice/saddle-points/src/main.rs @@ -0,0 +1,22 @@ +use std::collections::HashSet; +use saddle_points::*; + +fn main() { + let matrix = vec![ + vec![3, 5, 5], + vec![1, 8, 8], + vec![7, 0, 6], + ]; + + // let matrix = [vec![]]; + + let result = row_max_locations(&matrix); + + println!("{:?}", result); + // Output: [[1, 2], [1, 2], [0]] + + let cols: HashSet = [0, 2].into_iter().collect(); + let result2 = column_minima(&matrix, &cols); + println!("{:?}", result2); + +} \ No newline at end of file diff --git a/exercises/practice/say/src/lib.rs b/exercises/practice/say/src/lib.rs index 38ddc45ed85..81175315c50 100644 --- a/exercises/practice/say/src/lib.rs +++ b/exercises/practice/say/src/lib.rs @@ -1,3 +1,87 @@ +use std::iter::successors; + +const UP_TO_NINETEEN: [&str; 20] = [ + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "ten", + "eleven", + "twelve", + "thirteen", + "fourteen", + "fifteen", + "sixteen", + "seventeen", + "eighteen", + "nineteen"]; + +const TENS: [&str; 10] = [ + "zero", // for indexing, never accessed + "ten", + "twenty", + "thirty", + "forty", + "fifty", + "sixty", + "seventy", + "eighty", + "ninety" +]; + +const THOUSANDS: [&str; 7] = [ + "zero", // for indexing, never accessed + "thousand", + "million", + "billion", + "trillion", + "quadrillion", + "quintillion" +]; + pub fn encode(n: u64) -> String { - todo!("Say {n} in English."); + match n { + 0..20 => UP_TO_NINETEEN[n as usize].to_string(), + 20..=99 => { + let nb_tens = (n / 10) as usize; + match n % 10 { + 0 => TENS[nb_tens].to_string(), + units => format!("{}-{}", TENS[nb_tens], UP_TO_NINETEEN[units as usize]) + } + }, + 100..=999 => { + format_num(n, 100, "hundred") + }, + _ => { + // dealing with the thousands. 100_000 is a hundrer thousands + // Try to do it with a for loop, but exponential increment not simple + // Alternative to `.by_step()` for exponential growth is to use + // successors ! + let order = successors(Some(1u64), |n| n.checked_mul(1000)) + .find(|&i| i > n / 1000) + .unwrap(); + println!("{} {}", order, order / 1000); + format_num(n, order, THOUSANDS[((order as f64).log10() / 1000_f64.log10()) as usize]) + } + } } + +fn format_num(n: u64, div: u64, div_desc: &str) -> String { + match (n / div, n % div) { + (nb_div, 0) => format!("{} {}", UP_TO_NINETEEN[nb_div as usize], div_desc), + (nb_div, remainder) => format!("{} {} {}", encode(nb_div), div_desc, encode(remainder)) + } +} + +// Inspired by https://exercism.org/tracks/rust/exercises/say/solutions/LudwigStecher +// Last arm of the match end up been the simplest option +// Tried for loop that returns value but https://users.rust-lang.org/t/how-to-return-value-from-for-loop/79225/2 +// Tried ().by_step().find() but exponential growth not possible with equivalent of by_step() +// Turns out the .zip() choice is not that complicated since to find related +// element in THOUSANDS, we have the compute the log in base 1000 (which does not exist for (unsigned) int) diff --git a/exercises/practice/scrabble-score/Cargo.toml b/exercises/practice/scrabble-score/Cargo.toml index eacff3dd872..68dd039c4ad 100644 --- a/exercises/practice/scrabble-score/Cargo.toml +++ b/exercises/practice/scrabble-score/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +once_cell = "1.19" diff --git a/exercises/practice/scrabble-score/src/lib.rs b/exercises/practice/scrabble-score/src/lib.rs index d875e1977f6..e1c4e1a2b9d 100644 --- a/exercises/practice/scrabble-score/src/lib.rs +++ b/exercises/practice/scrabble-score/src/lib.rs @@ -1,4 +1,45 @@ +use std::collections::HashMap; +use once_cell::sync::Lazy; + +static GLOBAL_LETTERS_SCORE: Lazy> = Lazy::new(|| { + let mut m = HashMap::new(); + m.insert('a', 1); + m.insert('b', 3); + m.insert('c', 3); + m.insert('d', 2); + m.insert('e', 1); + m.insert('f', 4); + m.insert('g', 2); + m.insert('h', 4); + m.insert('i', 1); + m.insert('j', 8); + m.insert('k', 5); + m.insert('l', 1); + m.insert('m', 3); + m.insert('n', 1); + m.insert('o', 1); + m.insert('p', 3); + m.insert('q', 10); + m.insert('r', 1); + m.insert('s', 1); + m.insert('t', 1); + m.insert('u', 1); + m.insert('v', 4); + m.insert('w', 4); + m.insert('x', 8); + m.insert('y', 4); + m.insert('z', 10); + m +}); + /// Compute the Scrabble score for a word. pub fn score(word: &str) -> u64 { - todo!("Score {word} in Scrabble."); + word + .to_ascii_lowercase() + .chars() + .filter(|c| c.is_ascii_alphabetic()) + .filter_map(|c| { + GLOBAL_LETTERS_SCORE.get(&c) + }) + .sum() } diff --git a/exercises/practice/series/src/lib.rs b/exercises/practice/series/src/lib.rs index e73729f6c63..0d2253ac6b7 100644 --- a/exercises/practice/series/src/lib.rs +++ b/exercises/practice/series/src/lib.rs @@ -1,3 +1,4 @@ pub fn series(digits: &str, len: usize) -> Vec { - todo!("What are the series of length {len} in string {digits:?}") + let chars: Vec = digits.chars().collect(); + chars.windows(len).map(|chars| chars.iter().collect::()).collect() } diff --git a/exercises/practice/sieve/src/lib.rs b/exercises/practice/sieve/src/lib.rs index 23a75707c5c..652e1ed2979 100644 --- a/exercises/practice/sieve/src/lib.rs +++ b/exercises/practice/sieve/src/lib.rs @@ -1,3 +1,22 @@ pub fn primes_up_to(upper_bound: u64) -> Vec { - todo!("Construct a vector of all primes up to {upper_bound}"); + let candidates: Vec = (2..=upper_bound).collect(); + let nb_el: usize = (upper_bound - 1) as usize; + let mut mask: Vec = vec![true; nb_el ]; // Same size as `candidates`. 1:1 matching. + + // Iterate over `candidates` + for (i, &val) in candidates.iter().enumerate() { + if mask[i] { + // If prime, mark as not prime all multiples of `val` + for j in ((i+val as usize)..nb_el).step_by(val as usize) { + mask[j] = false; + } + } + } + + // Filter `candidates` based on `mask` + candidates + .into_iter() + .zip(mask) + .filter_map(|(val, is_prime)| if is_prime { Some(val) } else { None }) + .collect() } diff --git a/exercises/practice/simple-cipher/src/lib.rs b/exercises/practice/simple-cipher/src/lib.rs index b3ab8b14151..44a989fcc09 100644 --- a/exercises/practice/simple-cipher/src/lib.rs +++ b/exercises/practice/simple-cipher/src/lib.rs @@ -1,11 +1,61 @@ +use rand::{rng, Rng}; + + +fn is_key_invalid(key: &str) -> bool { + + if key.is_empty() { + return true; + } + + key + .chars() + .any(|c| !c.is_alphabetic() || !c.is_ascii_lowercase() || !c.is_lowercase()) +} + pub fn encode(key: &str, s: &str) -> Option { - todo!("Use {key} to encode {s} using shift cipher") + if is_key_invalid(key) { + return None; + } + + let mut res = String::new(); + for (c, k) in s.chars().zip(key.chars().cycle()) { + println!("encode {c}, {k}"); + println!("encode : {}, {}", c as u8, k as u8); + // res.push(('a' as u8 + (26 + k as u8 - c as u8) % 26 ) as char); + // res.push((((k as u8 - 'a' as u8) + c as u8) % 26 ) as char); + res.push((b'a' + (k as u8 - b'a' + c as u8 - b'a') % 26) as char); + } + + println!("{res}"); + + Some(res) } pub fn decode(key: &str, s: &str) -> Option { - todo!("Use {key} to decode {s} using shift cipher") + + if is_key_invalid(key) { + return None; + } + + let mut res = String::new(); + for (c, k) in s.chars().zip(key.chars().cycle()) { + println!("decode : {c}, {k}"); + println!("decode : {}, {}", c as u8, k as u8); + res.push((b'a' + (26 + c as u8 - k as u8) % 26 ) as char) + } + + println!("{res}"); + + Some(res) } pub fn encode_random(s: &str) -> (String, String) { - todo!("Generate random key with only a-z chars and encode {s}. Return tuple (key, encoded s)") + let mut rng = rng(); + let key: String = (0..100).map(|_| { + (b'a' + rng.random_range(0..26)) as char + }) + .collect(); + + let encoded = encode(&key, s); + (key, encoded.unwrap()) } diff --git a/exercises/practice/simple-linked-list/src/lib.rs b/exercises/practice/simple-linked-list/src/lib.rs index 75a931155b5..605eb3e9cd8 100644 --- a/exercises/practice/simple-linked-list/src/lib.rs +++ b/exercises/practice/simple-linked-list/src/lib.rs @@ -1,12 +1,32 @@ -pub struct SimpleLinkedList { +use std::iter::successors; + +struct Node where T: Clone{ + value: T, + next: Option>> +} + +impl Clone for Node { + fn clone(&self) -> Node { + Self { + value: self.value.clone(), + next: self.next.as_ref().map(|next_node| Box::new((**next_node).clone())) + } + } +} + +pub struct SimpleLinkedList { // Delete this field // dummy is needed to avoid unused parameter error during compilation - dummy: ::std::marker::PhantomData, + head: Option>>, + len: usize } -impl SimpleLinkedList { +impl SimpleLinkedList { pub fn new() -> Self { - todo!() + Self { + len: 0, + head: None + } } // You may be wondering why it's necessary to have is_empty() @@ -15,34 +35,64 @@ impl SimpleLinkedList { // whereas is_empty() is almost always cheap. // (Also ask yourself whether len() is expensive for SimpleLinkedList) pub fn is_empty(&self) -> bool { - todo!() + self.head.is_none() } pub fn len(&self) -> usize { - todo!() + self.len } pub fn push(&mut self, _element: T) { - todo!() + let node = Node::{ + value: _element, + next: self.head.take() + }; + self.head = Some(Box::new(node)); + self.len+=1; } pub fn pop(&mut self) -> Option { - todo!() + if let Some(ret) = self.head.take() { + self.head = ret.next; + self.len -= 1; + Some(ret.value) + } + else { + None + } } pub fn peek(&self) -> Option<&T> { - todo!() + if let Some(ret) = &self.head { + Some(&ret.value) + } + else { + None + } } #[must_use] pub fn rev(self) -> SimpleLinkedList { - todo!() + let mut ret = Self::new(); + + successors(self.head, |node| node.next.clone()) + .for_each(|node| { + ret.push(node.value); + }); + + ret } } -impl FromIterator for SimpleLinkedList { +impl FromIterator for SimpleLinkedList { fn from_iter>(_iter: I) -> Self { - todo!() + let mut ret: Self = Self::new(); + + _iter.into_iter().for_each(|el| { + ret.push(el); + }); + + ret } } @@ -57,8 +107,15 @@ impl FromIterator for SimpleLinkedList { // Please note that the "front" of the linked list should correspond to the "back" // of the vector as far as the tests are concerned. -impl From> for Vec { +impl From> for Vec { fn from(mut _linked_list: SimpleLinkedList) -> Vec { - todo!() + let mut ret:Vec = Vec::new(); + + while let Some(val) = _linked_list.pop() { + ret.push(val); + } + + ret.reverse(); + ret } } diff --git a/exercises/practice/space-age/src/lib.rs b/exercises/practice/space-age/src/lib.rs index fd180d47cf2..5d68f2a09b2 100644 --- a/exercises/practice/space-age/src/lib.rs +++ b/exercises/practice/space-age/src/lib.rs @@ -2,17 +2,24 @@ // In order to pass the tests you can add-to or change any of this code. #[derive(Debug)] -pub struct Duration; +pub struct Duration { + seconds: u64 +} + +const EARTH_SECOND_PER_YEAR : f64 = 31_557_600.0; impl From for Duration { fn from(s: u64) -> Self { - todo!("s, measured in seconds: {s}") + Self { + seconds: s + } } } pub trait Planet { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64; fn years_during(d: &Duration) -> f64 { - todo!("convert a duration ({d:?}) to the number of years on this planet for that duration"); + d.seconds as f64 / EARTH_SECOND_PER_YEAR / Self::ORBITAL_PERIOD_IN_EARTH_YEARS } } @@ -25,11 +32,27 @@ pub struct Saturn; pub struct Uranus; pub struct Neptune; -impl Planet for Mercury {} -impl Planet for Venus {} -impl Planet for Earth {} -impl Planet for Mars {} -impl Planet for Jupiter {} -impl Planet for Saturn {} -impl Planet for Uranus {} -impl Planet for Neptune {} +impl Planet for Mercury { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64 = 0.2408467; +} +impl Planet for Venus { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64 = 0.61519726; +} +impl Planet for Earth { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64 = 1.0; +} +impl Planet for Mars { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64 = 1.8808158; +} +impl Planet for Jupiter { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64 = 11.862615; +} +impl Planet for Saturn { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64 = 29.447498; +} +impl Planet for Uranus { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64 = 84.016846; +} +impl Planet for Neptune { + const ORBITAL_PERIOD_IN_EARTH_YEARS: f64 = 164.79132; +} diff --git a/exercises/practice/space-age/tests/space_age.rs b/exercises/practice/space-age/tests/space_age.rs index 995feedb133..08fd114021e 100644 --- a/exercises/practice/space-age/tests/space_age.rs +++ b/exercises/practice/space-age/tests/space_age.rs @@ -11,7 +11,7 @@ fn assert_in_delta(expected: f64, actual: f64) { #[test] fn age_on_earth() { let seconds = 1_000_000_000; - let duration = Duration::from(seconds); + let duration: Duration = Duration::from(seconds); let output = Earth::years_during(&duration); let expected = 31.69; assert_in_delta(expected, output); diff --git a/exercises/practice/spiral-matrix/src/lib.rs b/exercises/practice/spiral-matrix/src/lib.rs index 16d2d2ccd4e..79fa41a7bc5 100644 --- a/exercises/practice/spiral-matrix/src/lib.rs +++ b/exercises/practice/spiral-matrix/src/lib.rs @@ -1,3 +1,54 @@ pub fn spiral_matrix(size: u32) -> Vec> { - todo!("Function that returns the spiral matrix of square size {size}"); + let mut size: usize = size as usize; + let mut start_count = 1; + let mut start_pos = (0, 0); + + let mut res: Vec> = vec![vec![0; size]; size]; + + loop { + + // Empty square, return + if size == 0 { + return res; + } + + // Upper segment + for k in 0..size { + res[start_pos.0][start_pos.1 + k] = start_count; + start_count+=1; + } + + // Single cell square. + if size == 1 { + break; + } + + // Right segment + for k in 1..size { + res[start_pos.0 + k][start_pos.1 + size-1] = start_count; + start_count+=1; + } + + // Lower segment + for k in (0..=(size-2)).rev() { + res[start_pos.0 + size-1][start_pos.1 + k] = start_count; + start_count+=1; + } + + // Left segment + for k in (1..=(size-2)).rev() { + res[start_pos.0 + k][start_pos.1] = start_count; + start_count+=1; + } + + if size <= 2 { + break; + } + size -= 2; + start_pos = (start_pos.0+1, start_pos.1 + 1); + } + + + res + } diff --git a/exercises/practice/sublist/src/lib.rs b/exercises/practice/sublist/src/lib.rs index ac50a59cfd7..3a3095b49c6 100644 --- a/exercises/practice/sublist/src/lib.rs +++ b/exercises/practice/sublist/src/lib.rs @@ -7,7 +7,29 @@ pub enum Comparison { } pub fn sublist(first_list: &[i32], second_list: &[i32]) -> Comparison { - todo!( - "Determine if the {first_list:?} is equal to, sublist of, superlist of or unequal to {second_list:?}." - ); + if first_list == second_list { + return Comparison::Equal; + } + + if first_list.is_empty() { + return Comparison::Sublist; + } + + if second_list.is_empty() { + return Comparison::Superlist; + } + + if first_list.len() > second_list.len() { + return match sublist(second_list, first_list) { + Comparison::Sublist => Comparison::Superlist, + Comparison::Superlist => Comparison::Sublist, + other => other + }; + } + + if second_list.windows(first_list.len()).any(|x| x == first_list) { + return Comparison::Sublist; + } + + Comparison::Unequal } diff --git a/exercises/practice/sum-of-multiples/src/lib.rs b/exercises/practice/sum-of-multiples/src/lib.rs index c5e1645e6f1..2cdc36d0d90 100644 --- a/exercises/practice/sum-of-multiples/src/lib.rs +++ b/exercises/practice/sum-of-multiples/src/lib.rs @@ -1,3 +1,5 @@ pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { - todo!("Sum the multiples of all of {factors:?} which are less than {limit}") + (1..limit) + .filter(|n| factors.iter().any(|f| *f > 0 && n % f ==0)) + .sum() } diff --git a/exercises/practice/tournament/src/lib.rs b/exercises/practice/tournament/src/lib.rs index 780d3acf817..8f47fcd76eb 100644 --- a/exercises/practice/tournament/src/lib.rs +++ b/exercises/practice/tournament/src/lib.rs @@ -1,5 +1,152 @@ +use std::collections::HashMap; + +#[derive(Debug)] +struct Score { + matches_played: u32, + wins: u32, + draws: u32, + losses: u32, + points: u32 +} + +impl Score { + + fn new() -> Self { + Self { + matches_played: 0, + wins: 0, + draws: 0, + losses: 0, + points: 0 + } + } + + fn add_win(&mut self) { + self.matches_played += 1; + self.wins += 1; + self.points += 3; + } + + fn add_loss(&mut self) { + self.matches_played += 1; + self.losses += 1; + } + + fn add_draw(&mut self) { + self.matches_played += 1; + self.draws += 1; + self.points += 1; + } + +} + pub fn tally(match_results: &str) -> String { - todo!( - "Given the result of the played matches '{match_results}' return a properly formatted tally table string." - ); + let mut ret = String::new(); + let mut teams_scores: HashMap = HashMap::new(); + + // Process each line + for result in match_results.lines() { + // Parse results from line + let mut parts = result.split(";"); + let team1 = parts.next().unwrap(); + let team2 = parts.next().unwrap(); + let match_result = parts.next().unwrap(); + + // Update `teams_scores` according to current result + match match_result { + "win" => { + // Update score of `team1` + teams_scores.entry(team1.to_string()) + .and_modify(|v| { + v.add_win(); + }) + .or_insert_with(|| { + let mut win_score = Score::new(); + win_score.add_win(); + win_score + }); + // Update score of `team2` + teams_scores.entry(team2.to_string()) + .and_modify(|v| { + v.add_loss(); + }) + .or_insert_with(|| { + let mut loss_score = Score::new(); + loss_score.add_loss(); + loss_score + }); + }, + "draw" => { + // Update score for both teams (they both got a draw) + for team in [team1, team2] { + teams_scores.entry(team.to_string()) + .and_modify(|v| { + v.add_draw(); + }) + .or_insert_with(|| { + let mut draw_score = Score::new(); + draw_score.add_draw(); + draw_score + }); + } + }, + "loss" => { + // Update score of `team1` + teams_scores.entry(team1.to_string()) + .and_modify(|v| { + v.add_loss(); + }) + .or_insert_with(|| { + let mut loss_score = Score::new(); + loss_score.add_loss(); + loss_score + }); + // Update score of `team2` + teams_scores.entry(team2.to_string()) + .and_modify(|v| { + v.add_win(); + }) + .or_insert_with(|| { + let mut win_score = Score::new(); + win_score.add_win(); + win_score + }); + }, + _ => { + println!("Not supposed to happen") + } + } + } + + // Convert to vector of key-value pairs (HashMap are unordered) + let mut teams_scores_vec: Vec<_> = teams_scores.into_iter().collect(); + + // Sort + teams_scores_vec.sort_by(|(team1, score1), (team2, score2)| { + score2.points.cmp(&score1.points) // Descending total points + .then_with(|| { + team1.cmp(team2) // Alphabetical order + }) + }); + + // Generate output header + ret.push_str(&format!("{:<31}| MP | W | D | L | P", "Team")); + + // If empty, return header + if teams_scores_vec.is_empty() { + return ret; + } + + for (team, score) in teams_scores_vec { + ret.push('\n'); + ret.push_str(&format!("{:<31}| {:>2} | {:>2} | {:>2} | {:>2} | {:>2}", // use padding + team, + score.matches_played, + score.wins, + score.draws, + score.losses, + score.points)); + } + + ret } diff --git a/exercises/practice/triangle/Cargo.toml b/exercises/practice/triangle/Cargo.toml index 9bb92a6ca4f..7872f1c8caa 100644 --- a/exercises/practice/triangle/Cargo.toml +++ b/exercises/practice/triangle/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +num-traits = "0.2" [features] generic = [] diff --git a/exercises/practice/triangle/src/lib.rs b/exercises/practice/triangle/src/lib.rs index 020b2a4cfa0..f1348117313 100644 --- a/exercises/practice/triangle/src/lib.rs +++ b/exercises/practice/triangle/src/lib.rs @@ -1,21 +1,125 @@ -pub struct Triangle; +use num_traits::Zero; +use std::fmt::Debug; +// use std::ops::Add; -impl Triangle { - pub fn build(sides: [u64; 3]) -> Option { - todo!( - "Construct new Triangle from following sides: {sides:?}. Return None if the sides are invalid." - ); +#[derive(Debug)] +pub struct Triangle { + a: T, + b: T, + c: T +} + +impl Triangle { + pub fn build(sides: [T; 3]) -> Option> { + // All sides should have positive length + if sides.iter().any(|side| *side <= T::zero()) { + return None; + } + + // A triangle is defined as : the sum of any + // two sides length is greater than the remaining side + let [a, b, c] = sides; + if a + b < c { + return None; + } + if b + c < a { + return None; + } + if a + c < b { + return None; + } + + Some(Self { + a, + b, + c, + }) } pub fn is_equilateral(&self) -> bool { - todo!("Determine if the Triangle is equilateral."); + self.a == self.b && self.b == self.c } pub fn is_scalene(&self) -> bool { - todo!("Determine if the Triangle is scalene."); + if self.is_equilateral() { + return false; + } + if self.is_isosceles() { + return false; + } + true } pub fn is_isosceles(&self) -> bool { - todo!("Determine if the Triangle is isosceles."); + println!("{:#?}", self); + if self.a == self.b || self.a == self.c || self.b == self.c { + println!("Bla"); + true + } + else { + false + } } } + + +// This is another option that does not need any external crate. +// But using ::default() because it just happens to return 0 +// for each type, is kind of a cheat. + +// impl Triangle { +// pub fn build(sides: [T; 3]) -> Option> +// where ::Output: PartialOrd +// { +// // All sides should have positive length +// if sides.iter().any(|side| *side <= T::default()) { +// return None; +// } + +// // A triangle is defined as : the sum of any +// // two sides length is greater than the remaining side +// let [a, b, c] = sides; +// if a + b < c { +// return None; +// } +// if b + c < a { +// return None; +// } +// if a + c < b { +// return None; +// } + +// Some(Self { +// a, +// b, +// c, +// }) +// } + +// pub fn is_equilateral(&self) -> bool { +// self.a == self.b && self.b == self.c +// } + +// pub fn is_scalene(&self) -> bool { +// if self.is_equilateral() { +// return false; +// } +// if self.is_isosceles() { +// return false; +// } +// true +// } + +// pub fn is_isosceles(&self) -> bool { +// println!("{:#?}", self); +// if self.a == self.b || self.a == self.c || self.b == self.c { +// println!("Bla"); +// true +// } +// else { +// false +// } +// } +// } + + diff --git a/exercises/practice/two-bucket/src/lib.rs b/exercises/practice/two-bucket/src/lib.rs index ae20764d7b0..21dfc1638ac 100644 --- a/exercises/practice/two-bucket/src/lib.rs +++ b/exercises/practice/two-bucket/src/lib.rs @@ -1,3 +1,5 @@ +use std::cmp::max; + #[derive(PartialEq, Eq, Debug)] pub enum Bucket { One, @@ -16,6 +18,17 @@ pub struct BucketStats { pub other_bucket: u8, } +fn get_gcd(a: &u8, b: &u8) -> u8 { + let mut a = *a; + let mut b = *b; + while b != 0 { + let temp = b; + b = a % b; + a = temp; + } + a +} + /// Solve the bucket problem pub fn solve( capacity_1: u8, @@ -23,7 +36,106 @@ pub fn solve( goal: u8, start_bucket: &Bucket, ) -> Option { - todo!( - "Given one bucket of capacity {capacity_1}, another of capacity {capacity_2}, starting with {start_bucket:?}, find pours to reach {goal}, or None if impossible" - ); + + let mut res = BucketStats{ + moves:0, + goal_bucket: Bucket::One, + other_bucket: 2 + }; + + // Goal larger than both buckets: not possible + if goal > max(capacity_1, capacity_2) { + return None; + } + + // It is only possible to get multiples of the greatest common factor of both numbers. + let gcd = get_gcd(&capacity_1, &capacity_2); + if goal % gcd != 0 { + return None; + } + + // To be agnostic of bucket one or two. + let (size_b1, size_b2) = match *start_bucket { + Bucket::One => (capacity_1, capacity_2), + Bucket::Two => (capacity_2, capacity_1) + }; + + // Initial step, first half + let mut in_b1: u8 = match *start_bucket { + Bucket::One => capacity_1, + Bucket::Two => capacity_2 + }; + let mut in_b2: u8 = 0; + res.moves += 1; + + // Complete second half of first step + if in_b1 != goal { + if size_b2 == goal { + in_b2 = goal; + res.moves += 1; + } + else { + let to_xfer = in_b1.min(size_b2 - in_b2); + in_b2 += to_xfer; + in_b1 -= to_xfer; + res.moves += 1; + } + } + + // main loop + while in_b1 != goal && in_b2 != goal { + + // first half of a step + if in_b2 == size_b2 { + in_b2 = 0; + } + else { + in_b1 = size_b1; + } + res.moves += 1; + if in_b1 == goal || in_b2 == goal { + break; + } + + // second half of a step + let to_xfer = in_b1.min(size_b2 - in_b2); + in_b2 += to_xfer; + in_b1 -= to_xfer; + res.moves += 1; + if in_b1 == goal || in_b2 == goal { + break; + } + + } + + // Format output + // Handle goal_bucket + match *start_bucket { + Bucket::One => { + if in_b1 == goal { + res.goal_bucket = Bucket::One; + } + else { + res.goal_bucket = Bucket::Two; + } + }, + Bucket::Two => { + if in_b1 == goal { + res.goal_bucket = Bucket::Two; + } + else { + res.goal_bucket = Bucket::One; + } + } + } + + // Handle other_bucket + if in_b1 == goal { + res.other_bucket = in_b2; + } + else { + res.other_bucket = in_b1; + } + + Some(res) } diff --git a/exercises/practice/variable-length-quantity/src/lib.rs b/exercises/practice/variable-length-quantity/src/lib.rs index 15f55c68cc6..23e56c83cac 100644 --- a/exercises/practice/variable-length-quantity/src/lib.rs +++ b/exercises/practice/variable-length-quantity/src/lib.rs @@ -1,3 +1,5 @@ +use std::iter::successors; + #[derive(Debug, PartialEq, Eq)] pub enum Error { IncompleteNumber, @@ -5,10 +7,65 @@ pub enum Error { /// Convert a list of numbers to a stream of bytes encoded with variable length encoding. pub fn to_bytes(values: &[u32]) -> Vec { - todo!("Convert the values {values:?} to a list of bytes") + + let res: Vec = values + .iter() + .flat_map(|&num| { + + // Represent num in base 128 + let mut base128: Vec = successors(Some(num), |&n| { + if n >= 128 { + Some(n / 128) + } else { + None + } + }) + .map(|n| (n % 128) as u8) + .collect(); + + // Put it in right order + base128.reverse(); + + // Set MSB to 1 for all bytes but the last + for i in 0..base128.len().saturating_sub(1) { + base128[i] += 0x80; + } + + base128 + }) + .collect(); + + res } /// Given a stream of bytes, extract all numbers which are encoded in there. pub fn from_bytes(bytes: &[u8]) -> Result, Error> { - todo!("Convert the list of bytes {bytes:?} to a list of numbers") + + let mut slice = bytes; + let mut last_of_number: bool = false; + let mut res: Vec = Vec::new(); + let mut num: u32 = 0; + + while let Some((&byte, rest)) = slice.split_first() { + if byte & 0x80 != 0 { + last_of_number = false; + num <<= 7; + num += (byte & 0b0111_1111) as u32; + } + else { + num <<= 7; + num += byte as u32; + res.push(num); + num = 0; + last_of_number = true; + } + slice = rest; + } + + if !last_of_number { + return Err(Error::IncompleteNumber); + } + + Ok(res) + } diff --git a/exercises/practice/wordy/Cargo.toml b/exercises/practice/wordy/Cargo.toml index aad29393aea..f0e4c4301e1 100644 --- a/exercises/practice/wordy/Cargo.toml +++ b/exercises/practice/wordy/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] +regex = "1.10" [features] exponentials = [] diff --git a/exercises/practice/wordy/src/lib.rs b/exercises/practice/wordy/src/lib.rs index dff74fdace2..63de1cc65ed 100644 --- a/exercises/practice/wordy/src/lib.rs +++ b/exercises/practice/wordy/src/lib.rs @@ -1,3 +1,62 @@ +use regex::Regex; + pub fn answer(command: &str) -> Option { - todo!("Return the result of the command '{command}' or None, if the command is invalid."); + // let mut command = command; + + // Check it starts correctly + // if command.starts_with("What is") { + // command = command["What is".len()..].trim_start(); + // } + // else { + // return None; + // } + + let command = command.strip_prefix("What is ")?.trim_start(); + + // Capture the first number + let re_first_number = Regex::new(r"^(-?\d+)").ok()?; + let caps = re_first_number.captures(command)?; + let first_str = caps.get(1)?.as_str(); + let mut first: i32 = first_str.parse().ok()?; + + // Update command so only the remaining task is left + let mut command = command[first_str.len()..].trim_start(); + + // Match operations : exponential is treated separately + let re_op = Regex::new(r"(?x) + ^ + (?Pplus|minus|multiplied\sby|divided\sby)\s+(?P-?\d+) + | + raised\sto\sthe\s+(?P-?\d+)(st|nd|rd|th)\s+power + ").ok()?; + + // Handle the rest of the operations + while let Some(op_caps) = re_op.captures(command) { + if let Some(op) = op_caps.name("op1") { + let rhs: i32 = op_caps.name("rhs1")?.as_str().parse().ok()?; + match op.as_str() { + "plus" => first += rhs, + "minus" => first -= rhs, + "multiplied by" => first *= rhs, + "divided by" => first /= rhs, + _ => { + return None; + } + } + } + else if let Some(rh2) = op_caps.name("rhs2") { + let exp: u32 = rh2.as_str().parse().ok()?; + first = first.pow(exp); + } + + // Update command so only the remaining task is left + command = command[op_caps.get(0)?.as_str().len()..].trim_start(); + } + + // Make sure the whole thing have been dealt correctly, which means only '?' is left + if command != "?" { + return None; + } + + Some(first) }