diff --git a/config.json b/config.json index e41452fd..1516f84d 100644 --- a/config.json +++ b/config.json @@ -748,6 +748,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "wordy", + "name": "Wordy", + "uuid": "78d31713-0d88-4821-a586-4b36c46eaa44", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "high-scores", "name": "High Scores", diff --git a/exercises/practice/wordy/.docs/instructions.md b/exercises/practice/wordy/.docs/instructions.md new file mode 100644 index 00000000..aafb9ee5 --- /dev/null +++ b/exercises/practice/wordy/.docs/instructions.md @@ -0,0 +1,59 @@ +# Instructions + +Parse and evaluate simple math word problems returning the answer as an integer. + +## Iteration 0 — Numbers + +Problems with no operations simply evaluate to the number given. + +> What is 5? + +Evaluates to 5. + +## Iteration 1 — Addition + +Add two numbers together. + +> What is 5 plus 13? + +Evaluates to 18. + +Handle large numbers and negative numbers. + +## Iteration 2 — Subtraction, Multiplication and Division + +Now, perform the other three operations. + +> What is 7 minus 5? + +2 + +> What is 6 multiplied by 4? + +24 + +> What is 25 divided by 5? + +5 + +## Iteration 3 — Multiple Operations + +Handle a set of operations, in sequence. + +Since these are verbal word problems, evaluate the expression from left-to-right, _ignoring the typical order of operations._ + +> What is 5 plus 13 plus 6? + +24 + +> What is 3 plus 2 multiplied by 3? + +15 (i.e. not 9) + +## Iteration 4 — Errors + +The parser should reject: + +- Unsupported operations ("What is 52 cubed?") +- Non-math questions ("Who is the President of the United States") +- Word problems with invalid syntax ("What is 1 plus plus 2?") diff --git a/exercises/practice/wordy/.meta/config.json b/exercises/practice/wordy/.meta/config.json new file mode 100644 index 00000000..426cc2ef --- /dev/null +++ b/exercises/practice/wordy/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "Ephraim-nonso" + ], + "files": { + "solution": [ + "src/lib.cairo" + ], + "test": [ + "tests/wordy.cairo" + ], + "example": [ + ".meta/example.cairo" + ], + "invalidator": [ + "Scarb.toml" + ] + }, + "blurb": "Parse and evaluate simple math word problems returning the answer as an integer.", + "source": "Inspired by one of the generated questions in the Extreme Startup game.", + "source_url": "https://github.com/rchatley/extreme_startup" +} diff --git a/exercises/practice/wordy/.meta/example.cairo b/exercises/practice/wordy/.meta/example.cairo new file mode 100644 index 00000000..7d2cd24e --- /dev/null +++ b/exercises/practice/wordy/.meta/example.cairo @@ -0,0 +1,148 @@ +#[derive(Drop, PartialEq)] +enum Operator { + Plus, + Minus, + Multiply, + Divide, + Invalid, +} + +pub fn answer(question: ByteArray) -> i32 { + let words: Array = split_question_into_words(question); + assert!(words.len() >= 3, "syntax error"); + assert!(words[0] == @"What" && words[1] == @"is", "unknown operation"); + + let mut result = match parse_int(words[2]) { + Option::Some(s) => s, + Option::None => panic!("syntax error") + }; + + let mut i = 3; + while i < words.len() { + let op = parse_operator(words.at(i)); + i += 1; + + if op == Operator::Multiply || op == Operator::Divide { + assert!(i < words.len() && words[i] == @"by", "unknown operation"); + i += 1; + } else if op == Operator::Invalid { + assert!(parse_int(words.at(i - 1)) != Option::None, "unknown operation"); + } + + let num: i32 = match words.get(i) { + Option::Some(s) => { + match parse_int(s.unbox()) { + Option::Some(s) => s, + Option::None => panic!("syntax error"), + } + }, + Option::None => panic!("syntax error"), + }; + + result = match op { + Operator::Plus => result + num, + Operator::Minus => result - num, + Operator::Multiply => result * num, + Operator::Divide => { + assert!(num != 0, "unknown operation"); + result / num + }, + Operator::Invalid => panic!("unknown operation"), + }; + + i += 1; + }; + + result +} + + +fn split_question_into_words(question: ByteArray) -> Array { + let mut words: Array = ArrayTrait::new(); + let mut current_word = ""; + + let mut i = 0; + while i < question.len() { + let char = question[i]; + if char == ' ' { + if current_word.len() > 0 { + words.append(current_word); + current_word = ""; + } + } else if char == '?' { + if current_word.len() > 0 { + words.append(current_word); + current_word = ""; + } + break; + } else { + current_word.append_byte(char); + } + i += 1; + }; + + words +} + + +fn parse_int(num: @ByteArray) -> Option { + let mut result: Option = Option::Some(0); + let mut size = num.len(); + let mut i = 0; + + let mut is_signed = false; + if num.at(i).unwrap() == '-' { + is_signed = true; + i += 1; + } + + while i < size { + let re = char_to_digit(num[i]); + match re { + Option::Some(v) => { + if let Option::Some(num) = result { + result = Option::Some(num * 10 + v.into()); + } + }, + Option::None => { + result = Option::None; + break; + } + } + i += 1; + }; + + if let Option::Some(val) = result { + if (is_signed) { + result = Option::Some(val * -1) + } + } + result +} + +fn parse_operator(word: @ByteArray) -> Operator { + if (word == @"plus") { + Operator::Plus + } else if (word == @"minus") { + Operator::Minus + } else if (word == @"multiplied") { + Operator::Multiply + } else if (word == @"divided") { + Operator::Divide + } else { + Operator::Invalid + } +} + + +// Utility function to convert a char representing a digit into its numerical value (u32 equivalent) +fn char_to_digit(c: u8) -> Option { + let zero_ascii = '0'; + let nine_ascii = '9'; + + if c >= zero_ascii && c <= nine_ascii { + Option::Some(c - zero_ascii) + } else { + Option::None // Return None for invalid characters + } +} diff --git a/exercises/practice/wordy/.meta/tests.toml b/exercises/practice/wordy/.meta/tests.toml new file mode 100644 index 00000000..f812dfa9 --- /dev/null +++ b/exercises/practice/wordy/.meta/tests.toml @@ -0,0 +1,79 @@ +# 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. + +[88bf4b28-0de3-4883-93c7-db1b14aa806e] +description = "just a number" + +[bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0] +description = "addition" + +[79e49e06-c5ae-40aa-a352-7a3a01f70015] +description = "more addition" + +[b345dbe0-f733-44e1-863c-5ae3568f3803] +description = "addition with negative numbers" + +[cd070f39-c4cc-45c4-97fb-1be5e5846f87] +description = "large addition" + +[0d86474a-cd93-4649-a4fa-f6109a011191] +description = "subtraction" + +[30bc8395-5500-4712-a0cf-1d788a529be5] +description = "multiplication" + +[34c36b08-8605-4217-bb57-9a01472c427f] +description = "division" + +[da6d2ce4-fb94-4d26-8f5f-b078adad0596] +description = "multiple additions" + +[7fd74c50-9911-4597-be09-8de7f2fea2bb] +description = "addition and subtraction" + +[b120ffd5-bad6-4e22-81c8-5512e8faf905] +description = "multiple subtraction" + +[4f4a5749-ef0c-4f60-841f-abcfaf05d2ae] +description = "subtraction then addition" + +[312d908c-f68f-42c9-aa75-961623cc033f] +description = "multiple multiplication" + +[38e33587-8940-4cc1-bc28-bfd7e3966276] +description = "addition and multiplication" + +[3c854f97-9311-46e8-b574-92b60d17d394] +description = "multiple division" + +[3ad3e433-8af7-41ec-aa9b-97b42ab49357] +description = "unknown operation" + +[8a7e85a8-9e7b-4d46-868f-6d759f4648f8] +description = "Non math question" + +[42d78b5f-dbd7-4cdb-8b30-00f794bb24cf] +description = "reject problem missing an operand" + +[c2c3cbfc-1a72-42f2-b597-246e617e66f5] +description = "reject problem with no operands or operators" + +[4b3df66d-6ed5-4c95-a0a1-d38891fbdab6] +description = "reject two operations in a row" + +[6abd7a50-75b4-4665-aa33-2030fd08bab1] +description = "reject two numbers in a row" + +[10a56c22-e0aa-405f-b1d2-c642d9c4c9de] +description = "reject postfix notation" + +[0035bc63-ac43-4bb5-ad6d-e8651b7d954e] +description = "reject prefix notation" diff --git a/exercises/practice/wordy/Scarb.toml b/exercises/practice/wordy/Scarb.toml new file mode 100644 index 00000000..983459d0 --- /dev/null +++ b/exercises/practice/wordy/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "wordy" +version = "0.1.0" +edition = "2024_07" + +[dev-dependencies] +cairo_test = "2.8.2" diff --git a/exercises/practice/wordy/src/lib.cairo b/exercises/practice/wordy/src/lib.cairo new file mode 100644 index 00000000..6ecb8fd4 --- /dev/null +++ b/exercises/practice/wordy/src/lib.cairo @@ -0,0 +1,3 @@ +pub fn answer(question: ByteArray) -> i32 { + panic!("implement `answer`") +} diff --git a/exercises/practice/wordy/tests/wordy.cairo b/exercises/practice/wordy/tests/wordy.cairo new file mode 100644 index 00000000..41a03e04 --- /dev/null +++ b/exercises/practice/wordy/tests/wordy.cairo @@ -0,0 +1,219 @@ +use wordy::answer; + +#[test] +fn just_a_number() { + let input = "What is 5?"; + let output = answer(input); + let expected = 5; + + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn addition() { + let input = "What is 1 plus 1?"; + let output = answer(input); + let expected = 2; + + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn more_addition() { + let input = "What is 53 plus 2?"; + let output = answer(input); + let expected = 55; + + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn addition_with_negative_numbers() { + let input = "What is -1 plus -10?"; + let output = answer(input); + + let expected = -11; + + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn large_addition() { + let input = "What is 123 plus 45678?"; + let output = answer(input); + + let expected = 45801; + + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn subtraction() { + let input = "What is 4 minus -12?"; + let output = answer(input); + + let expected = 16; + + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn multiplication() { + let input = "What is -3 multiplied by 25?"; + let output = answer(input); + let expected = -75; + + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn division() { + let input = "What is 33 divided by -3?"; + let output = answer(input); + + let expected = -11; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn multiple_additions() { + let input = "What is 1 plus 1 plus 1?"; + let output = answer(input); + + let expected = 3; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn addition_and_subtraction() { + let input = "What is 1 plus 5 minus -2?"; + let output = answer(input); + + let expected = 8; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn multiple_subtraction() { + let input = "What is 20 minus 4 minus 13?"; + let output = answer(input); + + let expected = 3; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn subtraction_then_addition() { + let input = "What is 17 minus 6 plus 3?"; + let output = answer(input); + + let expected = 14; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn multiple_multiplication() { + let input = "What is 2 multiplied by -2 multiplied by 3?"; + let output = answer(input); + let expected = -12; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn addition_and_multiplication() { + let input = "What is -3 plus 7 multiplied by -2?"; + let output = answer(input); + + let expected = -8; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn multiple_division() { + let input = "What is -12 divided by 2 divided by -3?"; + let output = answer(input); + + let expected = 2; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +#[should_panic(expected: "unknown operation")] +fn unknown_operation() { + let input = "What is 52 cubed?"; + let output = answer(input); + + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +#[should_panic(expected: "unknown operation")] +fn non_math_question() { + let input = "Who is the President of the United States?"; + answer(input); +} + +#[test] +#[ignore] +#[should_panic(expected: "syntax error")] +fn reject_problem_missing_an_operand() { + let input = "What is 1 plus?"; + answer(input); +} + +#[test] +#[ignore] +#[should_panic(expected: "syntax error")] +fn reject_problem_with_no_operands_or_operators() { + let input = "What is?"; + answer(input); +} + +#[test] +#[ignore] +#[should_panic(expected: "syntax error")] +fn reject_two_operations_in_a_row() { + let input = "What is 1 plus plus 2?"; + answer(input); +} + +#[test] +#[ignore] +#[should_panic(expected: "syntax error")] +fn reject_two_numbers_in_a_row() { + let input = "What is 1 plus 2 1?"; + answer(input); +} + +#[test] +#[ignore] +#[should_panic(expected: "syntax error")] +fn reject_postfix_notation() { + let input = "What is 1 2 plus?"; + answer(input); +} + +#[test] +#[ignore] +#[should_panic(expected: "syntax error")] +fn reject_prefix_notation() { + let input = "What is plus 1 2?"; + answer(input); +}