diff --git a/balanced_delimiter/solutions/keppy.rb b/balanced_delimiter/solutions/keppy.rb new file mode 100644 index 0000000..6e70470 --- /dev/null +++ b/balanced_delimiter/solutions/keppy.rb @@ -0,0 +1,54 @@ +class ParseRules + # Rather than hardcoding each possible case into the StringParser, + # we abstract out the parsing rules into a class with one method for each + # allowed state. Then we can match on arbitrary parse tree values. + # The only restrictions to our language is that we assume each significant character is a delimiter, + # and that there are zero or more possible states 'in between' delimiters. + # There will never be a closing delimiter on the stack. + + def initialize(tree) + # These rules dictate what can come next in the state machine + tree.each do |leaf| + self.class.send(:define_method, leaf[:state].to_sym) do |next_state, stack| + if leaf[:possible_states].include?(next_state) + stack.push(next_state) + elsif leaf[:close_this_state] == next_state + stack.pop() + else + return false + end + end + end + end + +end + + +class StringParser + def initialize + @delimiters = "{[()]}" + @opening_delimiters = "{([" + @closing_delimiters = "}])" + @stack = [] + @parse_tree = [ + {:state => "[", :possible_states => ["{", "(", "["], :close_this_state => "]"}, + {:state => "{", :possible_states => ["{", "(", "["], :close_this_state => "}"}, + {:state => "(", :possible_states => ["{", "(", "["], :close_this_state => ")"} + ] + @parser = ParseRules.new(@parse_tree) + end + + def solve(character_set) + return false if character_set.length < 2 + character_set.chars.each do |char| + if @stack.empty? && @closing_delimiters.include?(char) + return false + elsif @stack.empty? && @opening_delimiters.include?(char) + @stack.push(char) + elsif @delimiters.include?(char) + @parser.method(@stack[-1].to_sym).call(char, @stack) + end + end + @stack.empty? ? true : false + end +end diff --git a/balanced_delimiter/solutions/keppy_test.rb b/balanced_delimiter/solutions/keppy_test.rb new file mode 100644 index 0000000..68ebc49 --- /dev/null +++ b/balanced_delimiter/solutions/keppy_test.rb @@ -0,0 +1,69 @@ + +require 'minitest/autorun' + +require_relative './keppy.rb' + +class ParseRulesTest < MiniTest::Unit::TestCase + def setup + @parse_tree = [ + {:state => "[", :possible_states => ["{", "(", "["], :close_this_state => "]"}, + {:state => "{", :possible_states => ["{", "(", "["], :close_this_state => "}"}, + {:state => "(", :possible_states => ["{", "(", "["], :close_this_state => ")"} + ] + end + + def test_parse_rules_with_existing_delim + # This is a stack that has seen an opening paren + stack = ["("] + parse_rules = ParseRules.new(@parse_tree) + parse_rules.method(:"(").call(")", stack) + assert_equal([], stack) + end + + def test_parse_rules_with_wrong_match + stack = ["{"] + parse_rules = ParseRules.new(@parse_tree) + result = parse_rules.method(:"{").call(")", stack) + assert_equal(false, result) + end + + def test_parse_rules_wrong_match_with_multiple_delims + stack = ["(", "{", "["] + parse_rules = ParseRules.new(@parse_tree) + result = parse_rules.method(:"[").call(")", stack) + assert_equal(false, result) + end + + def test_parse_rules_correct_match_multiple_delims + stack = ["(", "{", "["] + expected_stack = ["(", "{"] + parse_rules = ParseRules.new(@parse_tree) + result = parse_rules.method(:"[").call("]", stack) + assert_equal(expected_stack, stack) + end + +end + +class StringParserTest < MiniTest::Unit::TestCase + def test_closing_delim + parser = StringParser.new + assert_equal(false, parser.solve("]")) + end + + def test_open_delim + parser = StringParser.new + assert_equal(false, parser.solve("[")) + end + + def test_complicated_correct + parser = StringParser.new + chars = "()[]{}([{}])([]{})" + assert_equal(true, parser.solve(chars)) + end + + def test_complicated_incorrect + parser = StringParser.new + chars = "([)]([][])([})" + assert_equal(false, parser.solve(chars)) + end +end