This is the CalculatorAdvanced
Kata from the Kata gem. I've just finished it and would love some feedback on my solution.
CalculatorAdvanced Kata Add Method Allow the expression to handle new lines between numbers - example: "1\n2\n3" computes to 6 - example: "2,3\n4" computes to 9 - detail: Consecutive use of delimeters should raise an exception - example: "1,\n2" or "1\n,2" completed (Y|n): Calling method with a negative number will give an exception - detail: The exception should tell the user "negatives not allowed" - detail: The exception will list the negative number that was in the string - detail: The exception should list all negatives if there is more than one completed (Y|n): Diff and Product Method should raise the same exceptions as the add method - detail: Consecutive Delimiters - detail: Negative Numbers completed (Y|n): Define Custom Delimeters Allow the add method to accept a different delimiter - detail: The line of the string will contain "//[delimeter]\n... - detail: This line is optional and all previous tests should pass - example: "//[;]\n1;2" computes to 3 - detail: "1;2" should raise an exception completed (Y|n): Allow the diff method to accept a different delimiter like add - example: //[;]\n2;1 computes to 1 completed (Y|n): Allow the prod method to accept a different delimiter like add - example: //[;]\n2;1 computes to 2 completed (Y|n): Allow the div method to accept a different delimiter like add - example: //[;]\n3;2 computes to 1 completed (Y|n): Allow the add method to handle multiple different delimeters - example: multiple delimeters can be specified using "//[delimeter]...[delimeter]\n... - example: "//[*][;]\n1*2;3" computes to 6 - example: "//[*][;][#]\n5*4;3#2" computes to -4 - example: "//[#][;][*]\n1*2#3;4,5\n6" computes to 21 completed (Y|n): Allow the diff method to handle multiple different delimeters - example: "//[*][;]\n1*2;3" computes to -4 completed (Y|n): Allow the prod method to handle multiple different delimeters - example: "//[*][;]\n1*2;3" computes to 6 completed (Y|n): Allow the div method to handle multiple different delimeters - example: "//[*][;]\n1*2;3" computes to 0 completed (Y|n): ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓ ┃ Requirement ┃ Time ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the expression to handle new lines between numbers ┃ 00:37:20 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Calling method with a negative number will give an exception ┃ 00:20:55 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ should raise the same exceptions as the add method ┃ 00:08:41 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the add method to accept a different delimiter ┃ 00:22:51 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the diff method to accept a different delimiter like add ┃ 00:06:04 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the prod method to accept a different delimiter like add ┃ 00:01:49 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the div method to accept a different delimiter like add ┃ 00:01:46 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the add method to handle multiple different delimeters ┃ 00:55:48 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the diff method to handle multiple different delimeters ┃ 00:00:54 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the prod method to handle multiple different delimeters ┃ 00:00:38 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Allow the div method to handle multiple different delimeters ┃ 00:00:43 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━┛
And, here's the Class I created:
class Calculatoradvanced
attr_reader :expr
def expr=(expression='')
@expr = expression
validate_expression
end
alias_method :initialize, :expr=
def add
@values.inject(&:+)
end
def diff
@values.inject(&:-)
end
def prod
@values.inject(&:*)
end
def div
@values.inject(&:/)
end
private
def validate_expression
if @expr =~ /,\n/ || @expr =~ /\n,/
raise "Consecutive Delimiters"
end
split_string = ",\n"
if match = @expr.match(/\/\/([^\n]*)\n(.*)/m)
delimeters, @expr = match.captures
delimeters = delimeters.split(/[\[\]]/)
delimeters.reject! { |delimeter| delimeter == '' }
split_string = "#{Regexp.escape(delimeters.join)}" + split_string
end
@values = @expr.split(/[#{split_string}]/).map(&:to_i)
@negatives = @values.select { |value| value < 0 }
if (@negatives && @negatives.any?)
raise "negatives not allowed: #{@negatives.join(', ')}"
end
end
end
I'm really unsatisfied with the test for Consecutive Delimiters. If anyone knows of a good way to make that test more flexible, I'd love to hear it.
Here are my specs:
require 'spec_helper'
require 'calculatoradvanced'
describe Calculatoradvanced do
subject(:calc) { Calculatoradvanced.new(expression) }
context 'with an empty expression' do
let(:expression) { '' }
specify { expect { calc }.to_not raise_exception }
end
context 'with expression "1\n2\n3"' do
let(:expression) { "1\n2\n3" }
specify { expect { calc }.to_not raise_exception }
its(:add) { should eq(6) }
end
context 'with expression "2,3\n4"' do
let(:expression) { "2,3\n4" }
its(:add) { should eq(9) }
its(:expr) { should eq("2,3\n4") }
end
context 'with expression "1,\n2"' do
let(:expression) { "1,\n2" }
specify { expect { calc.add }.to raise_exception }
end
context 'with expression "1\n,2"' do
let(:expression) { "1\n,2" }
specify { expect { calc.add }.to raise_exception }
end
shared_examples_for 'expression validation' do
specify { expect { method }.to raise_exception(RuntimeError, /negatives not allowed/) }
specify { expect { method }.to raise_exception(RuntimeError, /-1, -2, -3/) }
end
context 'with expression "-1,-2\n-3"' do
let(:expression) { "-1,-2\n-3" }
context '.add' do
let(:method) { calc.add }
it_behaves_like 'expression validation'
end
context '.diff' do
let(:method) { calc.diff }
it_behaves_like 'expression validation'
end
context '.prod' do
let(:method) { calc.prod }
it_behaves_like 'expression validation'
end
end
context 'with expression "//[;]\n1;2"' do
let(:expression) { "//[;]\n1;2" }
its(:add) { should eq(3) }
end
context 'with expression "//[;]\n2;1"' do
let(:expression) { "//[;]\n2;1" }
its(:diff) { should eq(1) }
its(:prod) { should eq(2) }
end
context 'with expression "//[;]\n3;2"' do
let(:expression) { "//[;]\n3;2" }
its(:div) { should eq(1) }
end
context 'with expression "//[*][;]\n1*2;3"' do
let(:expression) { "//[*][;]\n1*2;3" }
its(:add) { should eq(6) }
its(:diff) { should eq(-4) }
its(:prod) { should eq(6) }
its(:div) { should eq(0) }
end
context 'with expression "//[*][;][#]\n5*4;3#2"' do
let(:expression) { "//[*][;][#]\n5*4;3#2" }
its(:diff) { should eq(-4) }
end
context 'with expression "//[#][;][*]\n1*2#3;4,5\n6"' do
let(:expression) { "//[#][;][*]\n1*2#3;4,5\n6" }
its(:add) { should eq(21) }
end
end