From dacaddf1351285f5ee7a9f1d7127ca8893fd028a Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Wed, 17 Aug 2016 17:51:21 -0500 Subject: [PATCH] Implement Bracket Push Full discussion of this change is at https://github.com/exercism/xrust/pull/178 Tests ---- Tests follow the canonical json file. Example ____ The example is a bit of an experiment in Dependency Injection. I'm willing to admit that it might make this solution worse. But it doesn't affect the students at all, just the example. The idea here is that brackets and how they pair is a configuration detail, not a core part of the "are_balanced" algorithm. So I've changed Brackets so that it accepts an Option of arbitrary brackets. Maybe you want to match "/" and "\", or "<" and ">". I dunno, I'm not you. Look at all the neat brackets you can use! https://en.wikipedia.org/wiki/Bracket But brackets has a default to fall back on should the dependency not be provided. Just using a bare Vec all over the place isn't great either (as far as Primitive Obsession goes, at least). So I've pulled that part out into its own named concept -- MatchingBrackets. MatchingBrackets should know if things are matched, right? So if can accept 2 unknown brackets and return the ones that are unmatched (which will always be 0 or 2) , then Brackets doesn't have to concern itself with what to push back into the array. It'll just shove in whatever `unmatched` returns. Currently the `closed_by` function lives in the wrong place. MatchingBrackets is about a collection of paired brackets, this function is concerned with a single set of paired brackets. The API for this should be `bracket.closed_by(other_bracket)` Instead of the nonsensical `brackets.closed_by(left_bracket, right_bracket)`. This will require the extraction of a new struct, though. And this example was already kind of overdone. Placing problems ---- I think @petertseng's instincts are right https://github.com/exercism/xrust/pull/178#issuecomment-242252735 But I moved it before Queen Attack because I think having Queen Attack followed by Sublist is a good combo. --- config.json | 1 + exercises/bracket-push/Cargo.lock | 4 + exercises/bracket-push/Cargo.toml | 3 + exercises/bracket-push/HINTS.md | 6 ++ exercises/bracket-push/example.rs | 79 +++++++++++++++++++ exercises/bracket-push/tests/bracket-push.rs | 82 ++++++++++++++++++++ problems.md | 1 + 7 files changed, 176 insertions(+) create mode 100644 exercises/bracket-push/Cargo.lock create mode 100644 exercises/bracket-push/Cargo.toml create mode 100644 exercises/bracket-push/HINTS.md create mode 100644 exercises/bracket-push/example.rs create mode 100644 exercises/bracket-push/tests/bracket-push.rs diff --git a/config.json b/config.json index a90b1f05f..0775fa7fd 100644 --- a/config.json +++ b/config.json @@ -24,6 +24,7 @@ "grade-school", "tournament", "robot-simulator", + "bracket-push", "queen-attack", "sublist", "space-age", diff --git a/exercises/bracket-push/Cargo.lock b/exercises/bracket-push/Cargo.lock new file mode 100644 index 000000000..11412f139 --- /dev/null +++ b/exercises/bracket-push/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "bracket-push" +version = "0.0.0" + diff --git a/exercises/bracket-push/Cargo.toml b/exercises/bracket-push/Cargo.toml new file mode 100644 index 000000000..198b1bdab --- /dev/null +++ b/exercises/bracket-push/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "bracket-push" +version = "0.0.0" diff --git a/exercises/bracket-push/HINTS.md b/exercises/bracket-push/HINTS.md new file mode 100644 index 000000000..eced3f2f4 --- /dev/null +++ b/exercises/bracket-push/HINTS.md @@ -0,0 +1,6 @@ +# Bracket Push in Rust + +Reading about these Rust topics may help you implement a solution. + +- Lifetimes and Structs: https://doc.rust-lang.org/book/lifetimes.html#impl-blocks +- From trait: https://doc.rust-lang.org/std/convert/trait.From.html diff --git a/exercises/bracket-push/example.rs b/exercises/bracket-push/example.rs new file mode 100644 index 000000000..6bfde522e --- /dev/null +++ b/exercises/bracket-push/example.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +pub struct Brackets { + raw_brackets: Vec, + pairs: MatchingBrackets, +} + +impl<'a> From<&'a str> for Brackets { + fn from(i: &str) -> Self { + Brackets::new(String::from(i), None) + } +} + +impl Brackets { + pub fn new(s: String, pairs: Option>) -> Self { + let p = match pairs { + Some(x) => MatchingBrackets::from(x), + None => MatchingBrackets::from(vec![('[', ']'), ('{', '}'), ('(', ')')]), + }; + + Brackets { + raw_brackets: s.chars().filter(|c| p.contains(&c)).collect::>(), + pairs: p, + } + } + + pub fn are_balanced(&self) -> bool { + let mut unclosed: Vec = Vec::new(); + + for &bracket in self.raw_brackets.iter() { + if let Some(last_unclosed) = unclosed.pop() { + unclosed.extend(self.pairs.unmatched(last_unclosed, bracket)); + } else { + unclosed.push(bracket); + } + } + + unclosed.is_empty() + } +} + +pub struct MatchingBrackets { + collection: HashMap, +} + +impl From> for MatchingBrackets { + fn from(v: Vec<(char, char)>) -> Self { + MatchingBrackets { collection: v.into_iter().collect::>() } + } +} + +impl MatchingBrackets { + fn contains(&self, other: &char) -> bool { + let known = self.collection.keys().chain(self.collection.values()).collect::>(); + known.contains(&other) + } + + fn closer_for(&self, k: &char) -> Option<&char> { + self.collection.get(k) + } + + fn closed_by(&self, l: char, r: char) -> bool { + match self.closer_for(&l) { + Some(&x) => r == x, + None => false, + } + } + + fn unmatched(&self, open: char, potential_close: char) -> Vec { + let mut ret: Vec = Vec::new(); + + if !self.closed_by(open, potential_close) { + ret.push(open); + ret.push(potential_close); + } + + ret + } +} diff --git a/exercises/bracket-push/tests/bracket-push.rs b/exercises/bracket-push/tests/bracket-push.rs new file mode 100644 index 000000000..1f04471b0 --- /dev/null +++ b/exercises/bracket-push/tests/bracket-push.rs @@ -0,0 +1,82 @@ +extern crate bracket_push; + +use bracket_push::*; + +#[test] +fn paired_square_brackets() { + assert!(Brackets::from("[]").are_balanced()); +} + +#[test] +#[ignore] +fn empty_string() { + assert!(Brackets::from("").are_balanced()); +} + +#[test] +#[ignore] +fn unpaired_brackets() { + assert!(!Brackets::from("[[").are_balanced()); +} + +#[test] +#[ignore] +fn wrong_ordered_brackets() { + assert!(!Brackets::from("}{").are_balanced()); +} + +#[test] +#[ignore] +fn paired_with_whitespace() { + assert!(Brackets::from("{ }").are_balanced()); +} + +#[test] +#[ignore] +fn simple_nested_brackets() { + assert!(Brackets::from("{[]}").are_balanced()); +} + +#[test] +#[ignore] +fn several_paired_brackets() { + assert!(Brackets::from("{}[]").are_balanced()); +} + +#[test] +#[ignore] +fn paired_and_nested_brackets() { + assert!(Brackets::from("([{}({}[])])").are_balanced()); +} + +#[test] +#[ignore] +fn unopened_closing_brackets() { + assert!(!Brackets::from("{[)][]}").are_balanced()); +} + +#[test] +#[ignore] +fn unpaired_and_nested_brackets() { + assert!(!Brackets::from("([{])").are_balanced()); +} + +#[test] +#[ignore] +fn paired_and_wrong_nested_brackets() { + assert!(!Brackets::from("[({]})").are_balanced()); +} + +#[test] +#[ignore] +fn math_expression() { + assert!(Brackets::from("(((185 + 223.85) * 15) - 543)/2").are_balanced()); +} + +#[test] +#[ignore] +fn complex_latex_expression() { + let input = "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \ + \\end{array}\\right)"; + assert!(Brackets::from(input).are_balanced()); +} diff --git a/problems.md b/problems.md index 6fd8a35e9..2e3938446 100644 --- a/problems.md +++ b/problems.md @@ -43,6 +43,7 @@ hexadecimal | Option, zip/fold/chars, map grade-school | struct, entry api, Vec, Option tournament | enum, sorting, hashmap, structs robot-simulator | Immutability, enum +bracket-push | From trait, stack or recursion queen-attack | struct, trait (optional), Result sublist | enum, generic over type space-age | Custom Trait, From Trait, Default Trait implementation