Skip to content

Instantly share code, notes, and snippets.

@bbuchalter
Created February 23, 2018 01:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbuchalter/439cf721d3c5f558b91413261f7e445f to your computer and use it in GitHub Desktop.
Save bbuchalter/439cf721d3c5f558b91413261f7e445f to your computer and use it in GitHub Desktop.
# Create a calculator that takes integers separated by the basic mathematical operators (+,-,*,/) and returns the result.
# You can use Ruby's mathamatical methods(+, -, *, /).
# The priority is on working code, not brevity.
# 7 + 2 - 3 / 4 * 5 = 5.25
#math_characters = '7 + 2 - 3 / 4 * 5'
# magic
#math_result = 5.25
#puts "Result of #{math_characters} is: #{math_result}"
# Sandi's four steps to refactoring a class extraction
# 1. parse the new code
# 2. parse and execute it
# 3. parse, execute, use result
# 4. delete unused code
# permutations of order of operations, excluding exponents and roots
# 1. multiply, divide
# 2. multiply, add
# 3. multiply, subtract
# 4. multiply, multiply
# 5. divide, multiple
# 6. divide, add
# 7. divide, subtract
# 8. divide, divide
# 9. add, multiply
# 10. add, divide
# 11. add, subtract
# 12. add, add
# 13. subtract, multiply
# 14. subtract, divide
# 15. subtract, add
# 16. subtract, subtract
require 'rspec/autorun'
module Equation
# @params equation[String]
def initialize(equation)
@equation = equation
end
private
attr_reader :equation
def equation_as_array
@equation_as_array ||= equation.split(" ")
end
end
class StringMath
include Equation
# @return Float
def result
first_number = equation_as_array[0].to_f
operation = equation_as_array[1].to_sym
second_number = equation_as_array[2].to_f
first_number.send(operation, second_number)
end
end
RSpec.describe StringMath do
subject { described_class.new(equation).result }
context "multiply numbers from a string" do
let(:equation) { "4 * 3" }
it { is_expected.to eq(12) }
end
context "divide numbers from a string" do
let(:equation) { "6 / 3" }
it { is_expected.to eq(2) }
end
context "addition numbers from a string" do
let(:equation) { "6 + 3" }
it { is_expected.to eq(9) }
end
context "subtract numbers from a string" do
let(:equation) { "6 - 3" }
it { is_expected.to eq(3) }
end
context "divides into non whole numbers" do
let(:equation) { "6 / 4" }
it { is_expected.to eq(6 / 4.0) }
end
end
class StringCalculator
include Equation
# Recurse through each sub-equation until there is only a single value left.
# @return Float
def result
if result_found?
equation.to_f
else
StringCalculator.new(simplified_equation).result
end
end
private
# @return Boolean
def result_found?
equation_as_array.length == 1
end
# Replace the first sub equation with the results of that equation
# @return String
def simplified_equation
equation.sub(sub_equation, sub_result_as_string)
end
# Extract the first sub equation from the current equation
# @return String
def sub_equation
@sub_equation ||= SubEquation.new(equation).extract
end
# Calculate the result of the sub_equation
# @return String
def sub_result_as_string
@sub_result ||= StringMath.new(sub_equation).result.to_s
end
end
RSpec.describe StringCalculator do
subject { described_class.new(equation).result }
context "given division followed by multiplication" do
let(:equation) { "6 / 3 * 5" }
it "follows the order of operations" do
expect(subject).to eq(10)
end
end
context "given multiplication followed by division" do
let(:equation) { "6 * 3 / 3" }
it "follows the order of operations" do
expect(subject).to eq(6)
end
end
context "given multiplication followed by division followed by addition" do
let(:equation) { "6 * 3 / 3 + 1" }
it "follows the order of operations" do
expect(subject).to eq(7)
end
end
context "given addition followed division" do
let(:equation) { "6 + 3 / 9" }
it "follows the order of operations" do
expect(subject).to eq(6 + 3 / 9.0)
end
end
context "given addition followed division" do
let(:equation) { "6 + 3 / 10" }
it "follows the order of operations" do
expect(subject).to eq(6.3)
end
end
context "many operations" do
let(:equation) { "7 + 2 - 3 / 4 * 5" }
it "follows the order of operations", focus: true do
expect(subject).to eq(5.25)
end
end
end
class NextOperation
include Equation
# @return Integer
def index
first_multiplication_or_division || first_addition_or_subtraction
end
private
def first_multiplication_or_division
[equation.index("*"), equation.index("/")].compact.min
end
def first_addition_or_subtraction
[equation.index("+"), equation.index("-")].compact.min
end
end
RSpec.describe NextOperation do
describe "#index" do
subject { described_class.new(equation).index }
context "given division followed by multiplication" do
let(:equation) { "6 / 3 * 5" }
it { is_expected.to eq 2 }
end
context "given division followed by addition" do
let(:equation) { "6 / 3 + 5" }
it { is_expected.to eq 2 }
end
context "given division followed by subtraction" do
let(:equation) { "6 / 3 - 5" }
it { is_expected.to eq 2 }
end
context "given division followed by division" do
let(:equation) { "6 / 3 / 5" }
it { is_expected.to eq 2 }
end
context "given addition followed by division" do
let(:equation) { "6 + 3 / 5" }
it { is_expected.to eq 6 }
end
context "given addition followed by subtraction" do
let(:equation) { "6 + 3 - 5" }
it { is_expected.to eq 2 }
end
context "given addition followed by addition" do
let(:equation) { "6 + 3 + 5" }
it { is_expected.to eq 2 }
end
context "given subtraction followed by multiplication" do
let(:equation) { "6 + 3 * 5" }
it { is_expected.to eq 6 }
end
context "given subtraction followed by division" do
let(:equation) { "6 - 3 / 5" }
it { is_expected.to eq 6 }
end
context "given subtraction followed by multiplication" do
let(:equation) { "6 - 3 * 5" }
it { is_expected.to eq 6 }
end
context "given subtraction followed by addition" do
let(:equation) { "6 - 3 + 5" }
it { is_expected.to eq 2 }
end
context "given subtraction followed by subtraction" do
let(:equation) { "6 - 3 - 5" }
it { is_expected.to eq 2 }
end
context "given addition followed by division" do
let(:equation) { "6 + 3 / 5" }
it { is_expected.to eq 6 }
end
context "given addition followed by multiplication" do
let(:equation) { "6 + 3 * 5" }
it { is_expected.to eq 6 }
end
context "given addition followed by addition" do
let(:equation) { "6 + 3 + 5" }
it { is_expected.to eq 2 }
end
context "given addition followed by subtraction" do
let(:equation) { "6 + 3 - 5" }
it { is_expected.to eq 2 }
end
context "given no operation" do
let(:equation) { "6" }
it { is_expected.to eq nil }
end
end
end
class SubEquation
include Equation
# @return String
def extract
if next_operation
sub_equation_as_string
else
equation
end
end
private
def sub_equation_as_string
"#{equation_as_array[next_operation-1]} #{equation_as_array[next_operation]} #{equation_as_array[next_operation+1]}"
end
def next_operation
@next_operation ||= NextOperation.new(equation_as_array).index
end
end
RSpec.describe SubEquation do
describe "#extract" do
subject { described_class.new(equation).extract }
context "given division followed by multiplication" do
let(:equation) { "6 / 3 * 5" }
it { is_expected.to eq "6 / 3" }
end
context "given division followed by addition" do
let(:equation) { "6 / 3 + 5" }
it { is_expected.to eq "6 / 3" }
end
context "given division followed by subtraction" do
let(:equation) { "6 / 3 - 5" }
it { is_expected.to eq "6 / 3" }
end
context "given division followed by division" do
let(:equation) { "6 / 3 / 5" }
it { is_expected.to eq "6 / 3" }
end
context "given addition followed by division" do
let(:equation) { "6 + 3 / 5" }
it { is_expected.to eq "3 / 5" }
end
context "given addition followed by subtraction" do
let(:equation) { "6 + 3 - 5" }
it { is_expected.to eq "6 + 3" }
end
context "given addition followed by addition" do
let(:equation) { "6 + 3 + 5" }
it { is_expected.to eq "6 + 3" }
end
context "given subtraction followed by multiplication" do
let(:equation) { "6 + 3 * 5" }
it { is_expected.to eq "3 * 5" }
end
context "given subtraction followed by division" do
let(:equation) { "6 - 3 / 5" }
it { is_expected.to eq "3 / 5" }
end
context "given subtraction followed by multiplication" do
let(:equation) { "6 - 3 * 5" }
it { is_expected.to eq "3 * 5" }
end
context "given subtraction followed by addition" do
let(:equation) { "6 - 3 + 5" }
it { is_expected.to eq "6 - 3" }
end
context "given subtraction followed by subtraction" do
let(:equation) { "6 - 3 - 5" }
it { is_expected.to eq "6 - 3" }
end
context "given addition followed by division" do
let(:equation) { "6 + 3 / 5" }
it { is_expected.to eq "3 / 5" }
end
context "given addition followed by multiplication" do
let(:equation) { "6 + 3 * 5" }
it { is_expected.to eq "3 * 5" }
end
context "given addition followed by addition" do
let(:equation) { "6 + 3 + 5" }
it { is_expected.to eq "6 + 3" }
end
context "given addition followed by subtraction" do
let(:equation) { "6 + 3 - 5" }
it { is_expected.to eq "6 + 3" }
end
context "given no operation" do
let(:equation) { "6" }
it { is_expected.to eq "6" }
end
context "given operations with non-whole numbers" do
let(:equation) { "2.0 * 0.33333333 / 1.2354" }
it { is_expected.to eq "2.0 * 0.33333333" }
end
context "given no operation with non-whole numbers" do
let(:equation) { "0.33333333" }
it { is_expected.to eq "0.33333333" }
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment