diff --git a/exercises/practice/dominoes/.meta/test_template.tera b/exercises/practice/dominoes/.meta/test_template.tera new file mode 100644 index 000000000..22335e994 --- /dev/null +++ b/exercises/practice/dominoes/.meta/test_template.tera @@ -0,0 +1,61 @@ +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = &[ + {% for domino in test.input.dominoes %} + ({{ domino.0 }}, {{domino.1 }}), + {% endfor %} + ]; + {%- if test.expected %} + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); + {%- else %} + assert!(dominoes::chain(input).is_none()); + {%- endif %} +} +{% endfor -%} + +type Domino = (u8, u8); + +fn assert_correct(input: &[Domino], output: Vec) { + if input.len() != output.len() { + panic!("Length mismatch for input {input:?}, output {output:?}"); + } else if input.is_empty() { + // and thus output.is_empty() + return; + } + + let mut output_sorted = output + .iter() + .map(|&d| normalize(d)) + .collect::>(); + output_sorted.sort_unstable(); + let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); + input_sorted.sort_unstable(); + if input_sorted != output_sorted { + panic!("Domino mismatch for input {input:?}, output {output:?}"); + } + + // both input and output have at least 1 element + // This essentially puts the first element after the last one, thereby making it + // easy to check whether the domino chains "wraps around". + { + let mut n = output[0].1; + let iter = output.iter().skip(1).chain(output.iter().take(1)); + for &(first, second) in iter { + if n != first { + panic!("Chaining failure for input {input:?}, output {output:?}") + } + n = second + } + } +} + +fn normalize(d: Domino) -> Domino { + match d { + (m, n) if m > n => (n, m), + (m, n) => (m, n), + } +} diff --git a/exercises/practice/dominoes/.meta/tests.toml b/exercises/practice/dominoes/.meta/tests.toml index ff6932826..08c8e08d0 100644 --- a/exercises/practice/dominoes/.meta/tests.toml +++ b/exercises/practice/dominoes/.meta/tests.toml @@ -1,13 +1,41 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# 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. + +[31a673f2-5e54-49fe-bd79-1c1dae476c9c] +description = "empty input = empty output" + +[4f99b933-367b-404b-8c6d-36d5923ee476] +description = "singleton input = singleton output" [91122d10-5ec7-47cb-b759-033756375869] description = "singleton that can't be chained" +[be8bc26b-fd3d-440b-8e9f-d698a0623be3] +description = "three elements" + [99e615c6-c059-401c-9e87-ad7af11fea5c] description = "can reverse dominoes" +[51f0c291-5d43-40c5-b316-0429069528c9] +description = "can't be chained" + +[9a75e078-a025-4c23-8c3a-238553657f39] +description = "disconnected - simple" + +[0da0c7fe-d492-445d-b9ef-1f111f07a301] +description = "disconnected - double loop" + +[b6087ff0-f555-4ea0-a71c-f9d707c5994a] +description = "disconnected - single isolated" + [2174fbdc-8b48-4bac-9914-8090d06ef978] description = "need backtrack" @@ -16,3 +44,6 @@ description = "separate loops" [cd061538-6046-45a7-ace9-6708fe8f6504] description = "nine elements" + +[44704c7c-3adb-4d98-bd30-f45527cf8b49] +description = "separate three-domino loops" diff --git a/exercises/practice/dominoes/tests/dominoes.rs b/exercises/practice/dominoes/tests/dominoes.rs index 00bec57b2..1153a3b24 100644 --- a/exercises/practice/dominoes/tests/dominoes.rs +++ b/exercises/practice/dominoes/tests/dominoes.rs @@ -1,166 +1,89 @@ -use crate::CheckResult::*; - -type Domino = (u8, u8); - -#[derive(Debug)] -enum CheckResult { - GotInvalid, // chain returned None - Correct, - ChainingFailure(Vec), // failure to match the dots at the right side of one domino with - // the one on the left side of the next - LengthMismatch(Vec), - DominoMismatch(Vec), // different dominoes are used in input and output -} - -fn normalize(d: Domino) -> Domino { - match d { - (m, n) if m > n => (n, m), - (m, n) => (m, n), - } -} - -fn check(input: &[Domino]) -> CheckResult { - let output = match dominoes::chain(input) { - None => return GotInvalid, - Some(o) => o, - }; - if input.len() != output.len() { - return LengthMismatch(output); - } else if input.is_empty() { - // and thus output.is_empty() - return Correct; - } - - let mut output_sorted = output - .iter() - .map(|&d| normalize(d)) - .collect::>(); - output_sorted.sort_unstable(); - let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); - input_sorted.sort_unstable(); - if input_sorted != output_sorted { - return DominoMismatch(output); - } - - // both input and output have at least 1 element - // This essentially puts the first element after the last one, thereby making it - // easy to check whether the domino chains "wraps around". - let mut fail = false; - { - let mut n = output[0].1; - let iter = output.iter().skip(1).chain(output.iter().take(1)); - for &(first, second) in iter { - if n != first { - fail = true; - break; - } - n = second - } - } - if fail { - ChainingFailure(output) - } else { - Correct - } -} - -fn assert_correct(input: &[Domino]) { - match check(input) { - Correct => (), - GotInvalid => panic!("Unexpectedly got invalid on input {input:?}"), - ChainingFailure(output) => { - panic!("Chaining failure for input {input:?}, output {output:?}") - } - LengthMismatch(output) => { - panic!("Length mismatch for input {input:?}, output {output:?}") - } - DominoMismatch(output) => { - panic!("Domino mismatch for input {input:?}, output {output:?}") - } - } -} - #[test] fn empty_input_empty_output() { let input = &[]; - assert_eq!(dominoes::chain(input), Some(vec![])); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn singleton_input_singleton_output() { let input = &[(1, 1)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] -fn singleton_that_cant_be_chained() { +fn singleton_that_can_t_be_chained() { let input = &[(1, 2)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] -fn no_repeat_numbers() { +fn three_elements() { let input = &[(1, 2), (3, 1), (2, 3)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn can_reverse_dominoes() { let input = &[(1, 2), (1, 3), (2, 3)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] -fn no_chains() { +fn can_t_be_chained() { let input = &[(1, 2), (4, 1), (2, 3)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_simple() { let input = &[(1, 1), (2, 2)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_double_loop() { let input = &[(1, 2), (2, 1), (3, 4), (4, 3)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_single_isolated() { let input = &[(1, 2), (2, 3), (3, 1), (4, 4)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn need_backtrack() { let input = &[(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn separate_loops() { let input = &[(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)]; - assert_correct(input); -} - -#[test] -#[ignore] -fn pop_same_value_first() { - let input = &[(2, 3), (3, 1), (1, 1), (2, 2), (3, 3), (2, 1)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] @@ -177,5 +100,56 @@ fn nine_elements() { (3, 4), (5, 6), ]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); +} + +#[test] +#[ignore] +fn separate_three_domino_loops() { + let input = &[(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]; + assert!(dominoes::chain(input).is_none()); +} +type Domino = (u8, u8); + +fn assert_correct(input: &[Domino], output: Vec) { + if input.len() != output.len() { + panic!("Length mismatch for input {input:?}, output {output:?}"); + } else if input.is_empty() { + // and thus output.is_empty() + return; + } + + let mut output_sorted = output + .iter() + .map(|&d| normalize(d)) + .collect::>(); + output_sorted.sort_unstable(); + let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); + input_sorted.sort_unstable(); + if input_sorted != output_sorted { + panic!("Domino mismatch for input {input:?}, output {output:?}"); + } + + // both input and output have at least 1 element + // This essentially puts the first element after the last one, thereby making it + // easy to check whether the domino chains "wraps around". + { + let mut n = output[0].1; + let iter = output.iter().skip(1).chain(output.iter().take(1)); + for &(first, second) in iter { + if n != first { + panic!("Chaining failure for input {input:?}, output {output:?}") + } + n = second + } + } +} + +fn normalize(d: Domino) -> Domino { + match d { + (m, n) if m > n => (n, m), + (m, n) => (m, n), + } }