Skip to content

Commit

Permalink
dominoes: sync
Browse files Browse the repository at this point in the history
  • Loading branch information
senekor committed Aug 15, 2024
1 parent 3596893 commit 30f2971
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 107 deletions.
61 changes: 61 additions & 0 deletions exercises/practice/dominoes/.meta/test_template.tera
Original file line number Diff line number Diff line change
@@ -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<Domino>) {
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::<Vec<Domino>>();
output_sorted.sort_unstable();
let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::<Vec<Domino>>();
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),
}
}
37 changes: 34 additions & 3 deletions exercises/practice/dominoes/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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"
182 changes: 78 additions & 104 deletions exercises/practice/dominoes/tests/dominoes.rs
Original file line number Diff line number Diff line change
@@ -1,166 +1,89 @@
use crate::CheckResult::*;

type Domino = (u8, u8);

#[derive(Debug)]
enum CheckResult {
GotInvalid, // chain returned None
Correct,
ChainingFailure(Vec<Domino>), // failure to match the dots at the right side of one domino with
// the one on the left side of the next
LengthMismatch(Vec<Domino>),
DominoMismatch(Vec<Domino>), // 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::<Vec<Domino>>();
output_sorted.sort_unstable();
let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::<Vec<Domino>>();
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]
Expand All @@ -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<Domino>) {
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::<Vec<Domino>>();
output_sorted.sort_unstable();
let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::<Vec<Domino>>();
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),
}
}

0 comments on commit 30f2971

Please sign in to comment.