HiveBrain v1.2.0
Get Started
← Back to all entries
snippetrubyMinor

Parse sentences and calculate mathematical results

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
parsecalculatesentencesandresultsmathematical

Problem

Content of task:


Given a string of words and numbers. Extract the expression including:



  • the operator: either addition or subtraction



  • the two numbers that we are operating on





Return the result of the calculation.


Example:


"Panda has 48 apples and loses 4" returns 44


"Jerry has 34 apples and gains 6" returns 40


"loses" and "gains" are the only two words describing operators.


Should be a nice little kata for you :)


Note: No fruit debts nor bitten apples = The numbers are integers and
no negatives


source: codewars.com

I want to optimise so ugly code:

def calculate(string)
  operator = '-' if string.split(' ').include? 'loses'
  operator = '+' if string.split(' ').include? 'gains'
  sum = 0
  count = 0
  n1 = 0
  n2 = 0
  string.split(' ').map do |s|
    if s.to_i != 0 && s.to_i.is_a?(Numeric)
      if count == 0
        n1 = s.to_i
        count += 1
      else
        n2 = s.to_i
      end
    end
  end
  n1.method(operator).(n2)
end


This code nice works(all tests was passed). But I don't want a large number of variables. Maybe have you some advice how to optimise this code? Thank you in advance.

Solution

The problem is that you are manipulating text without using Regular Expressions.

Regular expressions are a tool made specifically for text manipulation (search, delete, replace ... ) and are extremely good at their job.

The over-complication you experience comes from the use loops, conditionals and arithmetic to manipulate strings.

Here is a solution using regular expressions:

def text_calculation(text)
  a, b = text.split(/[^0-9]/).reject(&:empty?).map(&:to_i)
  text.include?("gains") ? a + b : a - b
end


First line explanation

text.split(/[^0-9]/)


This splits the text at every character that is not a digit (^ negates and [0-9] means from 0 to 9.

The output is this list:

["", "", "", "", "", "", "", "", "", "", "48", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "4"]


Almost what we want, we just need to remove (reject) the empty strings (empty?):

.reject(&:empty?)


So now we have ["48", "4"] that we must convert to integers before we can do arithmetic on, map applies a function to each item of a list and to_i converts to integer so .map(&:to_i)

Second line explanation

We add if we find "gains" else subtract (condition ? if_true : if_false is the Ternary operator)

.scan

We need to find the parts of the string that match a given regex, given that the regex /\d+\ matches all the subsequent strings of digits.

We must "find all regex matches in ruby", searching Google for such a task lands us on StackOverflow reveals that .scan is exactly what we can use. (The second Stack Overflow example is even about searching digits in a string!)

def text_calculation(text)
  a, b = text.scan(/\d+/).map(&:to_i)
  text.include?("gains") ? a + b : a - b
end


This code is more direct than splitting and filtering.

Code Snippets

def text_calculation(text)
  a, b = text.split(/[^0-9]/).reject(&:empty?).map(&:to_i)
  text.include?("gains") ? a + b : a - b
end
text.split(/[^0-9]/)
["", "", "", "", "", "", "", "", "", "", "48", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "4"]
.reject(&:empty?)
def text_calculation(text)
  a, b = text.scan(/\d+/).map(&:to_i)
  text.include?("gains") ? a + b : a - b
end

Context

StackExchange Code Review Q#139863, answer score: 7

Revisions (0)

No revisions yet.