diff --git a/concepts/error-handling/.meta/config.json b/concepts/error-handling/.meta/config.json new file mode 100644 index 00000000..52914445 --- /dev/null +++ b/concepts/error-handling/.meta/config.json @@ -0,0 +1,7 @@ +{ + "blurb": "", + "authors": [ + "misicnenad" + ], + "contributors": [] +} diff --git a/concepts/error-handling/about.md b/concepts/error-handling/about.md new file mode 100644 index 00000000..4f6c27f5 --- /dev/null +++ b/concepts/error-handling/about.md @@ -0,0 +1 @@ +# Error Handling diff --git a/concepts/error-handling/introduction.md b/concepts/error-handling/introduction.md new file mode 100644 index 00000000..e10b99d0 --- /dev/null +++ b/concepts/error-handling/introduction.md @@ -0,0 +1 @@ +# Introduction diff --git a/concepts/error-handling/links.json b/concepts/error-handling/links.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/concepts/error-handling/links.json @@ -0,0 +1 @@ +[] diff --git a/config.json b/config.json index 25f1bf09..eab90ceb 100644 --- a/config.json +++ b/config.json @@ -147,6 +147,18 @@ ], "prerequisites": [], "difficulty": 4 + }, + { + "slug": "largest-series-product", + "name": "Largest Series Product", + "uuid": "d0cae4fa-22a8-4604-9f0f-f90751245ce3", + "practices": [ + "enums", + "error-handling", + "control-flow" + ], + "prerequisites": [], + "difficulty": 4 } ], "foregone": [ @@ -228,6 +240,11 @@ "uuid": "6b6dafdb-7868-4089-855a-eb6735b5de5c", "slug": "arrays", "name": "Arrays" + }, + { + "uuid": "cf49c14b-ed9a-4df6-a43a-3923e8489900", + "slug": "error-handling", + "name": "Error Handling" } ], "key_features": [ diff --git a/exercises/practice/armstrong-numbers/.docs/instructions.md b/exercises/practice/armstrong-numbers/.docs/instructions.md index 1820ebcf..5e56bbe4 100644 --- a/exercises/practice/armstrong-numbers/.docs/instructions.md +++ b/exercises/practice/armstrong-numbers/.docs/instructions.md @@ -1,15 +1,13 @@ # Instructions -An [Armstrong number][armstrong-number] is a number that is the sum of its own -digits each raised to the power of the number of digits. +An [Armstrong number][armstrong-number] is a number that is the sum of its own digits each raised to the power of the number of digits. For example: - 9 is an Armstrong number, because `9 = 9^1 = 9` - 10 is _not_ an Armstrong number, because `10 != 1^2 + 0^2 = 1` - 153 is an Armstrong number, because: `153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153` -- 154 is _not_ an Armstrong number, because: - `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` +- 154 is _not_ an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` Write some code to determine whether a number is an Armstrong number. diff --git a/exercises/practice/beer-song/.docs/instructions.md b/exercises/practice/beer-song/.docs/instructions.md index 7d1ccd66..e909cfe3 100644 --- a/exercises/practice/beer-song/.docs/instructions.md +++ b/exercises/practice/beer-song/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Recite the lyrics to that beloved classic, that field-trip favorite: 99 Bottles -of Beer on the Wall. +Recite the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall. Note that not all verses are identical. diff --git a/exercises/practice/hello-world/.docs/instructions.md b/exercises/practice/hello-world/.docs/instructions.md index 42081ddc..c9570e48 100644 --- a/exercises/practice/hello-world/.docs/instructions.md +++ b/exercises/practice/hello-world/.docs/instructions.md @@ -3,8 +3,7 @@ The classical introductory exercise. Just say "Hello, World!". -["Hello, World!"][hello-world] is the traditional first program for beginning -programming in a new language or environment. +["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment. The objectives are simple: diff --git a/exercises/practice/largest-series-product/.docs/instructions.md b/exercises/practice/largest-series-product/.docs/instructions.md new file mode 100644 index 00000000..f297b57f --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to look for patterns in the long sequence of digits in the encrypted signal. + +The technique you're going to use here is called the largest series product. + +Let's define a few terms, first. + +- **input**: the sequence of digits that you need to analyze +- **series**: a sequence of adjacent digits (those that are next to each other) that is contained within the input +- **span**: how many digits long each series is +- **product**: what you get when you multiply numbers together + +Let's work through an example, with the input `"63915"`. + +- To form a series, take adjacent digits in the original input. +- If you are working with a span of `3`, there will be three possible series: + - `"639"` + - `"391"` + - `"915"` +- Then we need to calculate the product of each series: + - The product of the series `"639"` is 162 (`6 × 3 × 9 = 162`) + - The product of the series `"391"` is 27 (`3 × 9 × 1 = 27`) + - The product of the series `"915"` is 45 (`9 × 1 × 5 = 45`) +- 162 is bigger than both 27 and 45, so the largest series product of `"63915"` is from the series `"639"`. + So the answer is **162**. diff --git a/exercises/practice/largest-series-product/.docs/introduction.md b/exercises/practice/largest-series-product/.docs/introduction.md new file mode 100644 index 00000000..597bb5fa --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a government agency that has intercepted a series of encrypted communication signals from a group of bank robbers. +The signals contain a long sequence of digits. +Your team needs to use various digital signal processing techniques to analyze the signals and identify any patterns that may indicate the planning of a heist. diff --git a/exercises/practice/largest-series-product/.meta/config.json b/exercises/practice/largest-series-product/.meta/config.json new file mode 100644 index 00000000..c0fda61e --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "misicnenad" + ], + "files": { + "solution": [ + "src/lib.cairo", + "Scarb.toml" + ], + "test": [ + "src/tests.cairo" + ], + "example": [ + ".meta/example.cairo" + ] + }, + "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", + "source": "A variation on Problem 8 at Project Euler", + "source_url": "https://projecteuler.net/problem=8" +} diff --git a/exercises/practice/largest-series-product/.meta/example.cairo b/exercises/practice/largest-series-product/.meta/example.cairo new file mode 100644 index 00000000..0ca15a2f --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/example.cairo @@ -0,0 +1,112 @@ +#[derive(Drop, Debug, PartialEq)] +pub enum Error { + SpanTooLong, + InvalidDigit: u8, + NegativeSpan, + IndexOutOfBounds +} + +#[derive(Drop, Copy)] +struct Product { + value: u64, + from: u32, +} + +pub fn lsp(input: @ByteArray, span: i32) -> Result { + // validate span + if span == 0 { + return Result::Ok(1); + } + if span < 0 { + return Result::Err(Error::NegativeSpan); + } + + // shadowing to make span of unsigned type + let span: u32 = span.try_into().unwrap(); + + if span > input.len() { + return Result::Err(Error::SpanTooLong); + } + + // calculate first max product + // use '?' to propagate the error if it occurred + let product = product_from(input, span, 0, span, Product { value: 1, from: 0 })?; + + next_max_product(input, span, product.value, product) +} + +fn product_from( + input: @ByteArray, span: u32, from: u32, remaining: u32, product: Product +) -> Result { + if remaining == 0 { + return Result::Ok(product); + } + if from + remaining > input.len() { + return Result::Ok(Product { value: 0, from }); + } + + let digit = input.at(from).try_into_digit()?; + if digit != 0 { + return product_from( + input, + span, + from + 1, + remaining - 1, + Product { value: product.value * digit, from: product.from } + ); + } else { + return product_from(input, span, from + 1, span, Product { value: 1, from: from + 1 }); + } +} + +fn next_max_product( + input: @ByteArray, span: u32, max: u64, current_product: Product +) -> Result { + if current_product.from + span >= input.len() { + return Result::Ok(max); + } + + // safe to unwrap, we already processed this digit before + let reduced_value = current_product.value + / input.at(current_product.from).try_into_digit().unwrap(); + + let next_digit = input.at(current_product.from + span).try_into_digit()?; + + let product = if next_digit == 0 { + product_from( + input, + span, + current_product.from + span + 1, + span, + Product { value: 1, from: current_product.from + span + 1 } + )? + } else { + Product { value: reduced_value * next_digit, from: current_product.from + 1 } + }; + + if product.value > max { + next_max_product(input, span, product.value, product) + } else { + next_max_product(input, span, max, product) + } +} + +/// Helper functions +#[generate_trait] +impl ByteArrayCharToU64 of ByteArrayCharToU64Trait { + fn try_into_digit(self: @Option) -> Result { + if let Option::Some(char) = (*self) { + // ASCII digits are characters 48 through 57 + if 48 <= char && char <= 57 { + Result::Ok(char.into() - 48) + } else { + Result::Err(Error::InvalidDigit(char)) + } + } else { + Result::Err(Error::IndexOutOfBounds) + } + } +} + +#[cfg(test)] +mod tests; diff --git a/exercises/practice/largest-series-product/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml new file mode 100644 index 00000000..6c111adf --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/tests.toml @@ -0,0 +1,60 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[7c82f8b7-e347-48ee-8a22-f672323324d4] +description = "finds the largest product if span equals length" + +[88523f65-21ba-4458-a76a-b4aaf6e4cb5e] +description = "can find the largest product of 2 with numbers in order" + +[f1376b48-1157-419d-92c2-1d7e36a70b8a] +description = "can find the largest product of 2" + +[46356a67-7e02-489e-8fea-321c2fa7b4a4] +description = "can find the largest product of 3 with numbers in order" + +[a2dcb54b-2b8f-4993-92dd-5ce56dece64a] +description = "can find the largest product of 3" + +[673210a3-33cd-4708-940b-c482d7a88f9d] +description = "can find the largest product of 5 with numbers in order" + +[02acd5a6-3bbf-46df-8282-8b313a80a7c9] +description = "can get the largest product of a big number" + +[76dcc407-21e9-424c-a98e-609f269622b5] +description = "reports zero if the only digits are zero" + +[6ef0df9f-52d4-4a5d-b210-f6fae5f20e19] +description = "reports zero if all spans include zero" + +[5d81aaf7-4f67-4125-bf33-11493cc7eab7] +description = "rejects span longer than string length" + +[06bc8b90-0c51-4c54-ac22-3ec3893a079e] +description = "reports 1 for empty string and empty product (0 span)" + +[3ec0d92e-f2e2-4090-a380-70afee02f4c0] +description = "reports 1 for nonempty string and empty product (0 span)" + +[6d96c691-4374-4404-80ee-2ea8f3613dd4] +description = "rejects empty string and nonzero span" + +[7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74] +description = "rejects invalid character in digits" + +[5fe3c0e5-a945-49f2-b584-f0814b4dd1ef] +description = "rejects negative span" +include = false + +[c859f34a-9bfe-4897-9c2f-6d7f8598e7f0] +description = "rejects negative span" +reimplements = "5fe3c0e5-a945-49f2-b584-f0814b4dd1ef" diff --git a/exercises/practice/largest-series-product/Scarb.toml b/exercises/practice/largest-series-product/Scarb.toml new file mode 100644 index 00000000..63d10243 --- /dev/null +++ b/exercises/practice/largest-series-product/Scarb.toml @@ -0,0 +1,4 @@ +[package] +name = "largest_series_product" +version = "0.1.0" +edition = "2023_11" diff --git a/exercises/practice/largest-series-product/src/lib.cairo b/exercises/practice/largest-series-product/src/lib.cairo new file mode 100644 index 00000000..a2e94c08 --- /dev/null +++ b/exercises/practice/largest-series-product/src/lib.cairo @@ -0,0 +1,113 @@ +#[derive(Drop, Debug, PartialEq)] +pub enum Error { + SpanTooLong, + InvalidDigit: u8, + NegativeSpan, + IndexOutOfBounds +} + +#[derive(Drop, Copy)] +struct Product { + value: u64, + start_index: u32, +} + +pub fn lsp(input: @ByteArray, span: i32) -> Result { + // validate span + if span == 0 { + return Result::Ok(1); + } + if span < 0 { + return Result::Err(Error::NegativeSpan); + } + + // shadowing to make span of unsigned type + let span: u32 = span.try_into().unwrap(); + + if span > input.len() { + return Result::Err(Error::SpanTooLong); + } + + // calculate first max product + // use '?' to propagate the error if it occurred + let product = product_from(input, span, 0, span, Product { value: 1, start_index: 0 })?; + + max_product(input, span, product.value, product.start_index, product.value) +} + +fn product_from( + input: @ByteArray, span: u32, from: u32, remaining: u32, accumulated: Product +) -> Result { + if remaining == 0 { + return Result::Ok(accumulated); + } + if from + remaining > input.len() { + // we couldn't find a span of non-zero digits, + // so all products must be zero + return Result::Ok(Product { value: 0, start_index: from }); + } + + let digit = input.at(from).try_into_digit()?; + + if digit == 0 { + return product_from( + input, span, from + 1, span, Product { value: 1, start_index: from + 1 } + ); + } else { + return product_from( + input, + span, + from + 1, + remaining - 1, + Product { value: accumulated.value * digit, start_index: accumulated.start_index } + ); + } +} + +fn max_product( + input: @ByteArray, span: u32, previous: u64, from: u32, max: u64 +) -> Result { + if from + span >= input.len() { + return Result::Ok(max); + } + + let next_digit = input.at(from + span).try_into_digit()?; + + let next = if next_digit == 0 { + // every product that includes digit 0 will be 0, + // so calculate the next product after the digit 0 + product_from( + input, span, from + span + 1, span, Product { value: 1, start_index: from + span + 1 } + )? + } else { + // safe to unwrap, we already processed this digit before + let common_product = previous / input.at(from).try_into_digit().unwrap(); + Product { value: common_product * next_digit, start_index: from + 1 } + }; + + if next.value > max { + max_product(input, span, next.value, next.start_index, next.value) + } else { + max_product(input, span, next.value, next.start_index, max) + } +} + +/// Helper functions +#[generate_trait] +impl ByteArrayCharToU64 of ByteArrayCharToU64Trait { + fn try_into_digit(self: @Option) -> Result { + if let Option::Some(char) = (*self) { + // ASCII digits are characters 48 through 57 + if 48 <= char && char <= 57 { + Result::Ok(char.into() - 48) + } else { + Result::Err(Error::InvalidDigit(char)) + } + } else { + Result::Err(Error::IndexOutOfBounds) + } + } +} + +#[cfg(test)] +mod tests; diff --git a/exercises/practice/largest-series-product/src/tests.cairo b/exercises/practice/largest-series-product/src/tests.cairo new file mode 100644 index 00000000..361c13e9 --- /dev/null +++ b/exercises/practice/largest-series-product/src/tests.cairo @@ -0,0 +1,112 @@ +use largest_series_product::{lsp, Error}; + +#[test] +fn return_is_a_result() { + let string_digits: ByteArray = "29"; + assert!(lsp(@string_digits, 2).is_ok()); +} + +#[test] +fn finds_the_largest_product_when_span_equals_length() { + let string_digits: ByteArray = "29"; + assert_eq!(Result::Ok(18), lsp(@string_digits, 2)); +} + +#[test] +fn can_find_the_largest_product_of_2_with_numbers_in_order() { + let string_digits: ByteArray = "0123456789"; + assert_eq!(Result::Ok(72), lsp(@string_digits, 2)); +} + +#[test] +fn can_find_the_largest_product_of_2() { + let string_digits: ByteArray = "576802143"; + assert_eq!(Result::Ok(48), lsp(@string_digits, 2)); +} + +#[test] +fn can_find_the_largest_product_of_3_with_numbers_in_order() { + let string_digits: ByteArray = "0123456789"; + assert_eq!(Result::Ok(504), lsp(@string_digits, 3)); +} + +#[test] +fn can_find_the_largest_product_of_3() { + let string_digits: ByteArray = "1027839564"; + assert_eq!(Result::Ok(270), lsp(@string_digits, 3)); +} + +#[test] +fn can_find_the_largest_product_of_5_with_numbers_in_order() { + let string_digits: ByteArray = "0123456789"; + assert_eq!(Result::Ok(15120), lsp(@string_digits, 5)); +} + +#[test] +fn can_get_the_largest_product_of_a_big_number() { + let string_digits: ByteArray = "73167176531330624919225119674426574742355349194934"; + assert_eq!(Result::Ok(23520), lsp(@string_digits, 6)); +} + +#[test] +fn reports_zero_if_the_only_digits_are_zero() { + let string_digits: ByteArray = "0000"; + assert_eq!(Result::Ok(0), lsp(@string_digits, 2)); +} + +#[test] +fn reports_zero_if_all_spans_include_zero() { + let string_digits: ByteArray = "99099"; + assert_eq!(Result::Ok(0), lsp(@string_digits, 3)); +} + +#[test] +fn reports_zero_if_last_span_is_zero() { + let string_digits: ByteArray = "999099"; + assert_eq!(Result::Ok(729), lsp(@string_digits, 3)); +} + +#[test] +fn rejects_span_longer_than_string_length() { + let string_digits: ByteArray = "123"; + assert_eq!(Result::Err(Error::SpanTooLong), lsp(@string_digits, 4)); +} + +#[test] +fn reports_1_for_empty_string_and_0_span() { + let string_digits: ByteArray = ""; + assert_eq!(Result::Ok(1), lsp(@string_digits, 0)); +} + +#[test] +fn reports_1_for_nonempty_string_and_0_span() { + let string_digits: ByteArray = "1234"; + assert_eq!(Result::Ok(1), lsp(@string_digits, 0)); +} + +#[test] +fn rejects_empty_string_and_nonzero_span() { + let string_digits: ByteArray = ""; + assert_eq!(Result::Err(Error::SpanTooLong), lsp(@string_digits, 1)); +} + +#[test] +fn rejects_invalid_character_in_digits() { + let string_digits: ByteArray = "1234a5"; + assert_eq!(Result::Err(Error::InvalidDigit('a')), lsp(@string_digits, 2)); +} + +#[test] +fn rejects_negative_span() { + let string_digits: ByteArray = "12345"; + assert_eq!(Result::Err(Error::NegativeSpan), lsp(@string_digits, -1)); +} + +// Additional tests for this track +// Taken from https://github.com/exercism/python/blob/367faf4c12c172bbd981d41f4426f1ae2f0b1f49/exercises/practice/largest-series-product/largest_series_product_test.py#L72 +#[test] +fn euler_big_number() { + let string_digits: ByteArray = + "7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450"; + assert_eq!(Result::Ok(23_514_624_000), lsp(@string_digits, 13)); +} diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md index 739363db..4ffd2da5 100644 --- a/exercises/practice/leap/.docs/introduction.md +++ b/exercises/practice/leap/.docs/introduction.md @@ -3,8 +3,7 @@ A leap year (in the Gregorian calendar) occurs: - In every year that is evenly divisible by 4. -- Unless the year is evenly divisible by 100, in which case it's only a leap - year if the year is also evenly divisible by 400. +- Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. Some examples: @@ -12,7 +11,6 @@ Some examples: - 1900 was not a leap year as it's not divisible by 400. - 2000 was a leap year! -> For a delightful, four-minute explanation of the whole phenomenon of leap -> years, check out [this YouTube video][video]. - -[video]: https://www.youtube.com/watch?v=xX96xng7sAE +~~~~exercism/note +For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE). +~~~~