Are there more elegant ways to prevent negative numbers in Ruby? - ruby

Given that I'd like to do the following calculation:
total = subtotal - discount
Because discount might be greater than subtotal, there is code like the following:
class Calculator
def initialize(subtotal: subtotal, discount: discount)
#subtotal = subtotal
#discount = discount
end
def total
[subtotal - discount, 0].max
end
private
def subtotal
#subtotal
end
def discount
#discount
end
end
When seeing the [subtotal - discount, 0].max part or any similar code, I often have to pause and think.
Are there more elegant ways to handle this kind of calculation?

I think your solution is essentially correct, and probably the most readable besides a small refactor. I might change it slightly like so:
def total
final_total = subtotal - discount
[final_total, 0].max
end
The ruby expression [final_total, 0].max is essentially the traditional solution in mathematics for the same function: max {final_total, 0}. The difference is just notation and context. Once you see this max expression once or twice you can read it as follows: "final_total, but at least zero".
Perhaps if you use this expression more than once you can add another at_least_zero method or something like in Shiko's solution.

Thinking we can extend the Numeric class?
class Numeric
def non_negative
self > 0 ? self : 0
end
end
class Calculator
def initialize(subtotal: subtotal, discount: discount)
#subtotal = subtotal
#discount = discount
end
def total
(#subtotal - #discount).non_negative
end
end

A plain if statement might be easier to understand:
def total
if discount > subtotal
0
else
subtotal - discount
end
end

Some performance numbers:
user system total real
[i, 0.0].max 0.806408 0.001779 0.808187 ( 0.810676)
0.0 if i < 0.0 0.643962 0.001077 0.645039 ( 0.646368)
0.0 if i.negative? 0.625610 0.001680 0.627290 ( 0.629439)
Code:
require 'benchmark'
n = 10_000_000
Benchmark.bm do |benchmark|
benchmark.report('[value, 0.0].max'.ljust(18)) do
n.times do |i|
a = [-1*i, 0.0].max
end
end
benchmark.report('0.0 if value < 0.0'.ljust(18)) do
n.times do |i|
a = 0.0 if -1*i < 0.0
end
end
benchmark.report('0.0 if value.negative?'.ljust(18)) do
n.times do |i|
a = 0.0 if (-1*i).negative?
end
end
end

Just to clarify more, we need to add classes to be extended in core_ext.rb . file :
1) Create core_ext.rb file under config\initializers folder in your project.
2) Paste below as mentioned by #songyy in his answer:
class Numeric
def non_negative
self > 0 ? self : 0
end
end
Reference:
https://guides.rubyonrails.org/plugins.html#extending-core-classes

Related

Ruby: How to return positive integer or 0 if number is negative? [duplicate]

Given that I'd like to do the following calculation:
total = subtotal - discount
Because discount might be greater than subtotal, there is code like the following:
class Calculator
def initialize(subtotal: subtotal, discount: discount)
#subtotal = subtotal
#discount = discount
end
def total
[subtotal - discount, 0].max
end
private
def subtotal
#subtotal
end
def discount
#discount
end
end
When seeing the [subtotal - discount, 0].max part or any similar code, I often have to pause and think.
Are there more elegant ways to handle this kind of calculation?
I think your solution is essentially correct, and probably the most readable besides a small refactor. I might change it slightly like so:
def total
final_total = subtotal - discount
[final_total, 0].max
end
The ruby expression [final_total, 0].max is essentially the traditional solution in mathematics for the same function: max {final_total, 0}. The difference is just notation and context. Once you see this max expression once or twice you can read it as follows: "final_total, but at least zero".
Perhaps if you use this expression more than once you can add another at_least_zero method or something like in Shiko's solution.
Thinking we can extend the Numeric class?
class Numeric
def non_negative
self > 0 ? self : 0
end
end
class Calculator
def initialize(subtotal: subtotal, discount: discount)
#subtotal = subtotal
#discount = discount
end
def total
(#subtotal - #discount).non_negative
end
end
A plain if statement might be easier to understand:
def total
if discount > subtotal
0
else
subtotal - discount
end
end
Some performance numbers:
user system total real
[i, 0.0].max 0.806408 0.001779 0.808187 ( 0.810676)
0.0 if i < 0.0 0.643962 0.001077 0.645039 ( 0.646368)
0.0 if i.negative? 0.625610 0.001680 0.627290 ( 0.629439)
Code:
require 'benchmark'
n = 10_000_000
Benchmark.bm do |benchmark|
benchmark.report('[value, 0.0].max'.ljust(18)) do
n.times do |i|
a = [-1*i, 0.0].max
end
end
benchmark.report('0.0 if value < 0.0'.ljust(18)) do
n.times do |i|
a = 0.0 if -1*i < 0.0
end
end
benchmark.report('0.0 if value.negative?'.ljust(18)) do
n.times do |i|
a = 0.0 if (-1*i).negative?
end
end
end
Just to clarify more, we need to add classes to be extended in core_ext.rb . file :
1) Create core_ext.rb file under config\initializers folder in your project.
2) Paste below as mentioned by #songyy in his answer:
class Numeric
def non_negative
self > 0 ? self : 0
end
end
Reference:
https://guides.rubyonrails.org/plugins.html#extending-core-classes

How to tackle this Ruby candy store homework?

I have a class in university that asks students to learn three languages in
one semester. Like one is from really old languages such as Haskell, the other one should be from interpreter languages.
So, now I have to learn Ruby, and I need help. Let's say there is class that has
class Help
##array = Array.new
##count = 0
#store
#chocolate
#candy
#store_code
store is string (name of store)
chocolate, candy, store_code are integer (price, and code number)
Lets consider that I have an add function and call it twice
def add (s, i, i, i)
array = [s, i, i, i]
count += 1
end
store_a = Help.new
store_a.add (A, 20, 1, 100)
store_b = Help.new
store_b.add (B, 50, 1, 100)
Anyway, store_a chocolate price is 20
store_b chocolate price is 50 now
How do I make a function inside of class that calculates average of chocolate price? (I make the count variable for this, but I don't know if I need it or not).
This can be refactored and made shorter, also you can make use of class variables like you mentioned in the question using "##", but my goal here is to keep it basic so you can start grasping it and slowly moving to more advanced techniques and designs:
class Warehouse
attr_accessor :products_stores
def initialize
#products_stores = []
end
def add_product(args)
#products_stores << args
end
def product_price_avg
avg = 0
#products_stores.each do |o|
avg += o[:product].price
end
avg / #products_stores.count
end
end
class Store
attr_accessor :code
def initialize(code)
#code = code
end
end
class Chocolate
attr_accessor :price
def initialize(price)
#price = price
end
end
store_a = Store.new(100)
store_b = Store.new(200)
cheap_chocolate = Chocolate.new(20)
expensive_chocolate = Chocolate.new(50)
warehouse = Warehouse.new
warehouse.add_product({store: store_a, product: cheap_chocolate})
warehouse.add_product({store: store_b, product: expensive_chocolate})
puts warehouse.product_price_avg

Find percentage of two small numbers in ruby

I have two small numbers that I'd like to find the percentage of.
First number: 0.683789473684211
Second number: 0.678958333333333
I want to find out what percentage of the number is bigger or smaller. These happen to be small numbers, but they could be bigger. The first number COULD be 250, and the second number could be 0.3443435. What I'm TRYING to do is detect whether the first number is 25% bigger than the second number.
I tried using this:
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.0
end
end
But it kept saying I was dividing by zero
How would you do it?
Why not shoot straight for what you say you want to do?
class Numeric
def sufficiently_bigger?(n, proportion = 1.25)
self >= proportion * n
end
end
p 5.sufficiently_bigger? 4 # => true
p 5.sufficiently_bigger? 4.00001 # => false
This will default to a 25% larger check, but you can override the proportionality by supplying a different value as the second argument.
It's generally easier and avoids the need for an explicit zero-denominator check if you express ratios in product form rather than using division.
The basic implementation of your code looks correct to me. Can you provide the specific example and expected output that is producing that error?
Just because I was curious I took your code and executed it with a small test suite and had 3 passing tests.
require 'rubygems'
require 'test/unit'
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.00
end
end
class PercentageTeset < Test::Unit::TestCase
def test_25_is_50_percent_of_50
assert_equal (25.percent_of(50)), 50.0
end
def test_50_is_100_percent_of_50
assert_equal (50.percent_of(50)), 100.0
end
def test_75_is_150_percent_of_50
assert_equal (75.percent_of(50)), 150.0
end
end
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.0
end
end
p 0.683789473684211.percent_of(0.678958333333333)
--output:--
100.71155181602376
p 250.percent_of(0.3443435)
--output:--
72601.9222084924
p 0.000_001.percent_of(0.000_000_5)
--output:--
200.0
p 0.000_000_000_01.percent_of(0.000_000_000_01)
--output:--
100.0
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.0
end
end
numbers = [ 0.683789473684211, 0.678958333333333 ]
min_max = {min: numbers.min, max: numbers.max}
puts "%<min>f is #{min_max[:min].percent_of(min_max[:max])} of %<max>f" % min_max
This program has opinions in that it shows what percentage the minimal number is of the maximal number, and shows the numbers.
If you use %d for the String#format method, you will show 0's. Perhaps that was what you were referring to, not sure.
Edit: Using minmax as suggested.
class Numeric
def percent_of(n)
self.to_f / n.to_f * 100.0
end
end
numbers = [ 0.683789473684211, 0.678958333333333 ]
min_max = Hash.new
min_max[:min], min_max[:max] = numbers.minmax
puts "%<min>f is #{min_max[:min].percent_of(min_max[:max])} of %<max>f" % min_max
I like the first version as the hash is built as it is needed, rather than initalized and then built.

Ruby, how to take a specific percentage of elements of an array

In a testing context I need to take a specific percentage of the elements of an array.
The specifications of my request can be described in this test:
def test_percent_elements
array = [1,2,3,4,5,6,7,8,9,10]
assert_equal([], array.percent_elements(0))
assert_equal([1], array.percent_elements(1))
assert_equal([1], array.percent_elements(10))
assert_equal([1,2], array.percent_elements(11))
assert_equal([1,2,3,4,5], array.percent_elements(50))
assert_equal([1,2,3,4,5,6,7,8,9,10], array.percent_elements(100))
end
Which is the best way to solve this in Ruby?
I'd write:
class Array
def percent_elements(percent)
take((size * percent / 100.0).ceil)
end
end
My actual approach is this:
class Array
def percent_elements(percent)
total = self.length
elements = ((total * percent) / 100.to_f).ceil
self[0, elements]
end
end

Having trouble with a Ruby rounding error

I can't for the life of me figure out why my: generate_receipt method is returning 54.9 for my "imported dinner plates item" when it should equal 54.65. I wrote RSpec tests to confirm that the array is indeed returning the correct values.
47.50 + 4.75 + 2.40 = 54.65
Why is it returning 54.9 instead of 54.65?! Where is this rounding up occurring? How do I get it to return the correct value? I'm stumped.
describe :calcualte_sales_tax do
it "should return the correct array" do
calculate_sales_tax(#receipt).should eq([0, 4.75])
end
end
describe :calculate_import_tax do
it "should return the correct array" do
calculate_import_tax(#receipt).should eq([0.50, 2.40])
end
end
#receipt = {
"1"=>{:item=>"imported chocolates", :price=>10.00, :quantity=>1},
"2"=>{:item=>"imported dinner plates", :price=>47.50, :quantity=>1}
}
def generate_receipt(receipt)
n = 0
while n < receipt.length
receipt["#{n+1}"][:price]+=calculate_sales_tax(receipt)[n]
receipt["#{n+1}"][:price]+=calculate_import_tax(receipt)[n]
n+=1
end
receipt
end
def calculate_import_tax(receipt)
taxes = []
receipt.each do |k,v|
if (v[:item] =~ /imported/)
subtotal = v[:price]
# v[:price]+=(((((5 * subtotal)/100)*20.ceil) / 20.0))
# taxes<<(5 * subtotal)/100
taxes<<((5 * subtotal/100)*20).ceil/20.0.round(2)
else
taxes<<0
end
end
taxes
end
def calculate_sales_tax(receipt)
tax_free_items = ["book", "chocolate bar", "chocolates", "pills"]
taxes = []
receipt.each do |k,v|
if (v[:item] =~ /chocolate\s/) ||
(v[:item] =~ /chocolates/) ||
(v[:item] =~ /book/) ||
(v[:item] =~ /pills/)
taxes<<0
else
subtotal = v[:price]
# v[:price]+=(((((10 * subtotal)/100)*20.ceil) / 20.0))
# taxes<<(10 * subtotal)/100
taxes<<((10 * subtotal/100)*20).ceil/20.0
end
end
taxes
end
def generate_receipt(receipt)
n = 0
while n < receipt.length
puts receipt["#{n+1}"][:price].inspect
receipt["#{n+1}"][:price]+=calculate_sales_tax(receipt)[n].round(2)
puts receipt["#{n+1}"][:price].inspect
puts calculate_import_tax(receipt)[n]
receipt["#{n+1}"][:price]+=calculate_import_tax(receipt)[n].round(2)
puts receipt["#{n+1}"][:price].inspect
puts "-----"
n+=1
end
receipt
end
Returns:
47.5
52.25
2.65
54.9
The bug is in your calculate_import_tax method. It's returning 2.65, not 2.40.
EDIT:
Got it :).
receipt["#{n+1}"][:price]+=calculate_sales_tax(receipt)[n]
receipt["#{n+1}"][:price]+=calculate_import_tax(receipt)[n]
Those rows are updating the price of the receipt. Hence, your tests are running independently, but the sales tax is modifying the raw price before the import tax is calculated...
1) Ruby treat numbers without decimal part as INTEGERS. And WILL NOT create result that have decimal part unless computation include floating point number.
2) 10 * X / 100 * 20 == X * 2
3) LEGAL COUNTING may require specified accuracy, and so you may need to use specialized library for such number crunching.
Fast solution would be to change that code to:
10.0 * subtotal / 100.0 * 20.0
Now Ruby will treat all those numbers as float's.

Resources