input: "20+10/5-1*2"
I want to perform arithmetic operations on that string how can I do it without using eval method in ruby?
expected output: 20
While I hesitate to answer an interview question, and I am completely embarrassed by this code, here is one awful way to do it. I made it Ruby-only and avoided Rails helpers because it seemed more of a Ruby task and not a Rails task.
#
# Evaluate a string representation of an arithmetic formula provided only these operations are expected:
# + | Addition
# - | Subtraction
# * | Multiplication
# / | Division
#
# Also assumes only integers are given for numerics.
# Not designed to handle division by zero.
#
# Example input: '20+10/5-1*2'
# Expected output: 20.0
#
def eval_for_interview(string)
add_split = string.split('+')
subtract_split = add_split.map{ |v| v.split('-') }
divide_split = subtract_split.map do |i|
i.map{ |v| v.split('/') }
end
multiply_these = divide_split.map do |i|
i.map do |j|
j.map{ |v| v.split('*') }
end
end
divide_these = multiply_these.each do |i|
i.each do |j|
j.map! do |k, l|
if l == nil
k.to_i
else
k.to_i * l.to_i
end
end
end
end
subtract_these = divide_these.each do |i|
i.map! do |j, k|
if k == nil
j.to_i
else
j.to_f / k.to_f
end
end
end
add_these = subtract_these.map! do |i, j|
if j == nil
i.to_f
else
i.to_f - j.to_f
end
end
add_these.sum
end
Here is some example output:
eval_for_interview('1+1')
=> 2.0
eval_for_interview('1-1')
=> 0.0
eval_for_interview('1*1')
=> 1.0
eval_for_interview('1/1')
=> 1.0
eval_for_interview('1+2-3*4')
=> -9.0
eval_for_interview('1+2-3/4')
=> 2.25
eval_for_interview('1+2*3/4')
=> 2.5
eval_for_interview('1-2*3/4')
=> -0.5
eval_for_interview('20+10/5-1*2')
=> 20.0
eval_for_interview('20+10/5-1*2*4-2/6+12-1-1-1')
=> 31.0
Looking to work with a dataset of strings that store money amounts in these formats. For example:
$217.3M
$1.6B
$34M
€1M
€2.8B
I looked at the money gem but it doesn't look like it handles the "M, B, k"'s back to numbers. Looking for a gem that does do that so I can convert exchange rates and compare quantities. I need the opposite of the number_to_human method.
I would start with something like this:
MULTIPLIERS = { 'k' => 10**3, 'm' => 10**6, 'b' => 10**9 }
def human_to_number(human)
number = human[/(\d+\.?)+/].to_f
factor = human[/\w$/].try(:downcase)
number * MULTIPLIERS.fetch(factor, 1)
end
human_to_number('$217.3M') #=> 217300000.0
human_to_number('$1.6B') #=> 1600000000.0
human_to_number('$34M') #=> 34000000.0
human_to_number('€1M') #=> 1000000.0
human_to_number('€2.8B') #=> 2800000000.0
human_to_number('1000') #=> 1000.0
human_to_number('10.88') #=> 10.88
I decided to not be lazy and actually write my own function if anyone else wants this:
def text_to_money(text)
returnarray = []
if (text.count('k') >= 1 || text.count('K') >= 1)
multiplier = 1000
elsif (text.count('M') >= 1 || text.count('m') >= 1)
multiplier = 1000000
elsif (text.count('B') >= 1 || text.count('b') >= 1)
multiplier = 1000000000
else
multiplier = 1
end
num = text.to_s.gsub(/[$,]/,'').to_f
total = num * multiplier
returnarray << [text[0], total]
return returnarray
end
Thanks for the help!
Is there a simple Rails/Ruby helper function to help you convert human readable numbers to actual numbers?
Such as:
1K => 1000
2M => 2,000,000
2.2K => 2200
1,500 => 1500
50 => 50
5.5M => 5500000
test = {
'1K' => 1000,
'2M' => 2000000,
'2.2K' => 2200,
'1,500' => 1500,
'50' => 50,
'5.5M' => 5500000
}
class String
def human_readable_to_i
multiplier = {'K' => 1_000, 'M' => 1_000_000}[self.upcase[/[KM](?=\z)/]] || 1
value = self.gsub(/[^\d.]/, '')
case value.count('.')
when 0 then value.to_i
when 1 then value.to_f
else 0
end * multiplier
end
end
test.each { |k, v| raise "Test failed" unless k.human_readable_to_i == v }
Try something like this if you have an array of human readable numbers than
array.map do |elem|
elem = elem.gsub('$','')
if elem.include? 'B'
elem.to_f * 1000000000
elsif elem.include? 'M'
elem.to_f * 1000000
elsif elem.include? 'K'
elem.to_f * 1000
else
elem.to_f
end
end
Have a look here as well, you will find many Numbers Helpers
NumberHelper Rails.
Ruby Array human readable to actual
Why is this code not printing 0 to 50 inclusive?
i = 0
until i <= 50 do
print i
i += 1
end
Either use
until i > 50 do
# ...
end
or
while i <= 50 do
# ...
end
Here's a more "Ruby like" example:
(0..50).each do |i|
puts i
end
Ugh.
i = 0
until i <= 50 do
print i
i += 1
end
That would generate 51 iterations because you're starting at 0 and trying to run until 50 is reached, except that until is "notting" your condition. If you want to loop perhaps while would be a better test:
i = 0
while i <= 50 do
print i
i += 1
end
>> 01234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950nil
But, even with while there are still 51 values being output:
i = 0
output = []
while i <= 50 do
output << i
i += 1
end
output.size # => 51
If you want to loop 50 times, why not use:
50.times do |i|
puts i
end
Or:
50.times { |i| puts i }
Change until to while. until is basically the same thing as while, but the conditional is inverted.
Another iterative method to use would be upto:
0.upto(50) do |i|
puts i
end
I really love this method for quick number iterations. It's super idiomatic (it does what it says) and it's inclusive of both start and end values so you don't have to calculate/account for an exclusive end val.
until stops executing when the condition it has is true. Because it is true from the beginning, nothing happens.
Just swap the comparison operators.
i = 0
until i > 50 do
print i
i += 1
end
You could also do
i = 0
while i <= 50 do
print i
i += 1
end
Use Idiomatic Ruby
Other answers have addressed why your original code doesn't work, and have pointed out the logic error in your conditional. However, it's worth noting that a more idiomatic way to do what you're doing would avoid the conditional altogether. For example:
(1..50).each { |i| pp i }
This works. ;)
i = 1
while i < 51 do
print i
i += 1
end
the first thing you have the comparison sing backwards
you want to do something like:
i = 0
until i >= 50 do
print i
i += 1
end
you can take a look at http://ruby-doc.org/core-2.1.2/doc/syntax/control_expressions_rdoc.html#label-until+Loop for more info
Does anybody have any code handy that center truncates a string in Ruby on Rails?
Something like this:
Ex: "Hello World, how are you?" => "Hel...you?"
If you want a certain fixed length irrespective of the length of the string, you can use Rails #truncate:
s.truncate(100, omission: "...#{s.last(50)}")
Here is a modified version of Mike Woodhouse's answer. It takes 2 optional params: a minimum length for the the string to be ellipsisized and the edge length.
class String
def ellipsisize(minimum_length=4,edge_length=3)
return self if self.length < minimum_length or self.length <= edge_length*2
edge = '.'*edge_length
mid_length = self.length - edge_length*2
gsub(/(#{edge}).{#{mid_length},}(#{edge})/, '\1...\2')
end
end
"abc".ellipsisize #=> "abc"
"abcdefghi".ellipsisize #=> "abcdefghi"
"abcdefghij".ellipsisize #=> "abc...hij"
"abcdefghij".ellipsisize(4,4) #=> "abcd...ghij"
"Testing all paramas and checking them!".ellipsisize(6,5) #=> "Testi...them!"
How about a Regex version:
class String
def ellipsisize
gsub(/(...).{4,}(...)/, '\1...\2')
end
end
"abc".ellipsisize #=> "abc"
"abcdefghi".ellipsisize #=> "abcdefghi"
"abcdefghij".ellipsisize #=> "abc...hij"
EDIT: as suggested in the comments, parameterised length (and using a different Regex notation, just for the heck of it)
class String
def ellipsisize(len = 9)
len = 9 unless len > 9 # assumes minimum chars at each end = 3
gsub(%r{(...).{#{len-5},}(...)}, '\1...\2')
end
end
so...
"abcdefghij".ellipsisize #=> "abc...hij"
but we can also:
"abcdefghij".ellipsisize(10) #=> "abcdefghij"
Here is my suggestion:
s[3...-4] = "..." if s.length > 9
Since you didn't specify how many characters you want to truncate, I'll assume (from your example) that you want to truncate strings whose length is greater than six. You can then use something like this:
s = "Hello World, how are you?"
s = s[0, 3] + "..." + s[-3, 3] if s.length > 9
=> "Hel...ou?"
Just adapt the ranges if you need to truncate more characters.
class String
# https://gist.github.com/1168961
# remove middle from strings exceeding max length.
def ellipsize(options={})
max = options[:max] || 40
delimiter = options[:delimiter] || "..."
return self if self.size <= max
remainder = max - delimiter.size
offset = remainder / 2
(self[0,offset + (remainder.odd? ? 1 : 0)].to_s + delimiter + self[-offset,offset].to_s)[0,max].to_s
end unless defined? ellipsize
end
Another way:
class String
def middle_truncate(len)
return self if len >= size
return self[0...len] unless len > 4
half = len / 2.0
(result = dup)[(half - 1.5).floor...(1.5 - half).floor] = '...'
result
end
end
This has the added benefit of just truncating if string size < 5.
e.g. for an even-sized string:
2.1.1 :001 > s = "12345678901234567890"
=> "12345678901234567890"
2.1.1 :002 > s.middle_truncate 21
=> "12345678901234567890"
2.1.1 :003 > s.middle_truncate 20
=> "12345678901234567890"
2.1.1 :004 > s.middle_truncate 19
=> "12345678...34567890"
2.1.1 :005 > s.middle_truncate 18
=> "1234567...34567890"
2.1.1 :006 > s.middle_truncate 5
=> "1...0"
2.1.1 :007 > s.middle_truncate 4
=> "1234"
and for an odd-sized string:
2.1.1 :012 > s = "123456789012345678901"
=> "123456789012345678901"
2.1.1 :013 > s.middle_truncate 22
=> "123456789012345678901"
2.1.1 :014 > s.middle_truncate 21
=> "123456789012345678901"
2.1.1 :015 > s.middle_truncate 20
=> "12345678...345678901"
2.1.1 :016 > s.middle_truncate 19
=> "12345678...45678901"
2.1.1 :017 > s.middle_truncate 5
=> "1...1"
2.1.1 :018 > s.middle_truncate 4
=> "1234"
Modified rails version that only truncated from the middle
def middle_truncate(str, total: 30, lead: 15, trail: 15)
str.truncate(total, omission: "#{str.first(lead)}...#{str.last(trail)}")
end
I added position option to grosser's method:
def ellipsize(str, options={})
max = options[:max] || 40
delimiter = options[:delimiter] || "..."
position = options[:position] || 0.8
return str if str.size <= max
remainder = max - delimiter.size
offset_left = remainder * position
offset_right = remainder * (1 - position)
(str[0,offset_left + (remainder.odd? ? 1 : 0)].to_s + delimiter + str[-offset_right,offset_right].to_s)[0,max].to_s
end
If you need to limit your string length by bytesize and you need to handle unicode then the solutions listed won't work correctly.
Here's a solution that uses byte size and keeps unicode text elements intact (which are not always the same as unicode characters). Tested in Ruby 1.8/1.9/2.0.
It could be improved to be more efficient for very long strings that need to be truncated down to much smaller lengths.
You must install the unicode gem.
require 'unicode'
# truncates a unicode string like:
# >> truncate_string_middle('12345678', 5)
# => "1...8"
def truncate_string_middle(str, limit, ellipsis='...')
raise "limit (#{limit}) must not be less than the ellipsis size (#{ellipsis.bytesize})" if limit < ellipsis.bytesize
return str if str.bytesize <= limit
chars = Unicode.text_elements(str)
split_point = (chars.size/2.0).ceil
front, back = chars[0...split_point], chars[split_point..-1]
pop_front = chars.size.odd?
# alternate between popping from the front and shifting from the back until it's small enough
while (front.join + ellipsis + back.join).bytesize > limit
if pop_front
front.pop
else
back.shift
end
pop_front = !pop_front
end
front.join + ellipsis + back.join
end
This is the easiest way for me:
def ellipsisize(text, minimum_length=12,edge_length=4)
leftover = text.length - minimum_length
edge_length = leftover if (edge_length > leftover && leftover >= 0)
edge_length = 0 if leftover < 0
return text.truncate(minimum_length) << text.last(edge_length)
end
Regards folks!
A combination of Benjamin Sullivan's and khelll's answers. Uses built-in Rails stuff and lets you define edge length.
With String#truncate, I don't think there's a need to define minimum length at all. This solution automatically shortens the string if the shortened version is shorter than the input string.
def ellipsize(string, edge_length, separator: '…')
string.truncate(
edge_length * 2 + separator.size, omission: "#{separator}#{string.last(edge_length)}"
)
end
Here's my version that lets you specify the maximum length instead, so, you can ensure that a string doesn't exceed the required length:
class String
def truncate(maximum_length = 3, separator = '…')
return '' if maximum_length.zero?
return self if self.length <= maximum_length
middle_length = self.length - maximum_length + separator.length
edges_length = (self.length - middle_length) / 2.0
left_length = edges_length.ceil
right_length = edges_length.floor
left_string = left_length.zero? ? '' : self[0, left_length]
right_string = right_length.zero? ? '' : self[-right_length, right_length]
return "#{left_string}#{separator}#{right_string}"
end
end
'123456'.truncate(0) # ""
'123456'.truncate(1) # "…"
'123456'.truncate(2) # "1…"
'123456'.truncate(3) # "1…6"
'123456'.truncate(4) # "12…6"
'123456'.truncate(5) # "12…56"
'123456'.truncate(6) # "123456"
'123456'.truncate(7) # "123456"