Having a method update/reload its self everytime it gets called? Ruby - ruby

My long term goal is replicate this spreadsheet in a ruby program and then to a rails app.
Currently I am trying to make it determine which of the two debts has the highest interest then subtract the minimum amount from that debt as well as any extra amounts the person is willing to pay as well as subtracting the minimum value from the other debt.
example:
card = balance: $10,000, Minimum: $200, i% = 20%
loan = balance: $40,000, Minimum: $400, i% = 5%
payments made per month = $1000
In this case, the program would every month firstly take $600 ($1000 -($200 + $400) + $200) from the card until it's balance was 0 then take $1000 ($1000 - $400 + $400) until the loan was payed off and return how many months that would take.
Currently, I am trying to get the amount to subtract each month take into account the balance of the debt and have this update whenever the method is called - however this does not seem to be working and will stay at $400 for both debts (snowball_amount method). EDIT: missing method issue fixed. Needed to change attr_reader to attr_accessorAlso for some reason, when I pass a debt object into highest_interest, i'm getting an undefined method 'balance=' error. Would be grateful for some help with this!
Create a Debt Class
class Debt
def initialize(balance: b, monthly_payment: m, annual_interest_rate: a)
#balance = balance
#monthly_min = monthly_payment
#int_rate = annual_interest_rate
end
attr_reader :monthly_min, :balance, :int_rate
end
Create two debt objects
#debt1 = Debt.new(balance: 14000.0, monthly_payment: 200.0, annual_interest_rate: 0.06)
#debt2 = Debt.new(balance: 40000.0, monthly_payment: 400.0, annual_interest_rate: 0.08)
Put them into array
#debts_array = [#debt1, #debt2]
Set the amount the person is willing to pay each month
#payment = 1000.0
Determine how much extra is being payed i.e. #payment - each debts monthly minimum, only if that debt's balance is over 0
def snowball_amount
#payments_less_mins = #payment
#debts_array.each do |debt|
if debt.balance <= 0
#payments_less_mins
elsif debt.balance > 0
#payments_less_mins = #payments_less_mins - debt.monthly_min
end
end
puts #payments_less_mins
return #payments_less_mins
end
Method for calculating the balance of that debt for that month
def compounding_interest(balance, apr)
return balance * (1 + apr/12)**1
end
Determing how long it will take to pay the debt off. While the balance of the debt is above 0 firstly update the balance in line with the addition of interest, then subtract from the debt balance the minimum monthly payment and the snowball(extra amount) from the balance. Then set the debts balance to 0
def highest_interest(debt, snowball)
months_to_pay = 0
while debt.balance > 0
debt.balance = compounding_interest(debt.balance, debt.int_rate)
debt.balance = debt.balance - (debt.monthly_min + snowball)
months_to_pay += 1
end
debt.balance = 0
snowball_amount
puts months_to_pay
end
Determine which debt has the highest balance and then do the highest interest method on that debt.
def which_has_higher_interest
debts_array = #debts_array.sort{|i| i.int_rate}.reverse!
puts debts_array[0].balance
debts_array.each do |debt|
highest_interest(debt, snowball_amount)
end
end
Calling the which_has_higher_interest method
puts which_has_higher_interest

In lines 3, 4, and 7 of your highest_interest method, you are calling a method called balance= on a Debt object, but your Debt objects do not have such a method. You need to define it somehow, possibly by changing the line
attr_reader :monthly_min, :balance, :int_rate
to
attr_reader :monthly_min, :int_rate
attr_accessor :balance

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

How to give discounts in simple Ruby shopping cart?

I'm trying to make a very simple Ruby shopping cart, and I need to be able to give discounts if a user buys certain combinations of goods. These are indicated in the #costs - if bulk is true, a user gets a discount (of :bulk_price) for buying :bulk_num of goods. I've got it making basic charges, but now I need to subtract discounts in certain cases. Here's what I have so far:
class Cart
attr_accessor :total, :costs, :item_array, :subtotal
def initialize
#item_array=[]
#subtotal=0
#costs= [{"A"=>{:price=>2, :bulk=>true, :bulk_num=>4, :bulk_price=>7}}, {"B"=>{:price=>12, :bulk=> false}},{"C"=>{:price=>1.25,:bulk=>true, :bulk_num=>6, :bulk_price=>6}}, {"D"=>{:price=>0.15, :bulk=>false}}]
end
def scan(*items)
items.each do |item|
#item_array<<item
#costs.each do |cost|
if cost.has_key?(item)
#subtotal+=cost[item][:price]
end
end
#subtotal
end
end
def total
end
end
Now, I've created an array to keep track of which items are purchased, and I'd ideally like to have the total function check the array and subtract from the subtotal if needed. Maybe I've just been staring at this too long, but I am having trouble figuring that out. Could anyone help?
A few things:
Indent your code properly, it will make it much easier for you in the long run.
Remove :total from attr_accessor, it isn't needed and the generated total method will be overridden by the one you define later on.
Consider making each item an object which knows its own cost, rather than looking up the cost of each item in #costs. Conceptually, it doesn't make sense for a "shopping cart" to keep track of all the prices of all the items in your store.
Make your total method functional. Don't bother subtracting from #subtotal -- it will cause problems if total is called more than once.
Actually, subtotal would also be better if you recalculate whenever needed:
def subtotal
#item_array.reduce(0) { |sum,item| sum + (#costs[item][:price] || 0) }
end
It may not be obvious to you now, but writing your code "functionally", like this, makes it easier to avoid bugs. You can cache values if they are really expensive to calculate, and will be needed more than once, but in this case there's no need to.
For total, you can do something like:
def total
result = self.subtotal
# check which discounts apply and subtract from 'result'
result
end
Since your question involves an exercise, I decided to change it around a bit to make some points that you might find helpful. A few notes:
I renamed scan to checkout, lest the former be confused with String#scan
An order quantity is given for each item ordered, in the form of a hash that is passed to the checkout method;
I changed :bulk_price to a unit price that applies if :bulk is true and the quantity ordered is at least :bulk_num.
I changed #costs to a hash, because you need to access item names, which are now keys.
I moved #costs outside the class, for two reasons. Firstly, that data is likely to change, so it really shouldn't be hardwired in the class definiation. Secondly, doing that provides flexibility should you want different class instances to use different #costs. You'll see I chose to pass that hash as an argument when creating a new class instance.
I eliminated all your accessors.
An exception is now raised if you enter an item name that is not a key in #costs.
This is the approach I took:
class Cart
def initialize(costs)
#costs= costs
end
def checkout(items)
purchases = {}
items.each do |(item, qty)|
cost = #costs[item]
raise ArgumentError, "Item '#{item}' not in #costs array" \
if cost == nil
if cost[:bulk] && qty >= cost[:bulk_num]
tot_cost = qty.to_f * cost[:bulk_price]
discount = qty.to_f * (cost[:price] - cost[:bulk_price])
else
tot_cost = qty.to_f * cost[:price]
discount = 0.0
end
purchases[item] = {qty: qty, tot_cost: tot_cost, discount: discount}
end
purchases
end
def tot_cost(purchases)
purchases.values.reduce(0) {|tot, h| tot + h[:tot_cost]}
end
def tot_discount(purchases)
purchases.values.reduce(0) {|tot, h| tot + h[:discount]}
end
end
costs = {"A"=>{price: 2, bulk: true, bulk_num: 4, bulk_price: 1.75},
"B"=>{price: 12, bulk: false },
"C"=>{price: 1.25, bulk: true, bulk_num: 6, bulk_price: 1.00},
"D"=>{price: 0.15, bulk: false }}
cart = Cart.new(costs)
purchases = cart.checkout({"A"=>6, "B"=>7, "C"=>4}) # item => quantity purchased
p purchases # => {"A"=>{:qty=>6, :tot_cost=>10.5, :discount=>1.5},
# => "B"=>{:qty=>7, :tot_cost=>84.0, :discount=>0.0},
# => "C"=>{:qty=>4, :tot_cost=>5.0, :discount=>0.0}}
p cart.tot_cost(purchases) # => 99.5
p cart.tot_discount(purchases) # => 1.5

How to recalculate values when the new year comes, Ruby

I'm trying to write an app that calculates sick/vacation days and how much an employee has available in either category.
Next step is adding a method that every year on January first recalculates all the employee's available vacation/sick time (without scrubbing/overwriting the data from the previous year, as that information needs to be accessed).
I have an Employee model (which is posted below), which :has_many Furloughs (which have their own model which I won't post unless requested, but basically handles how the date ranges are calculated).
class Employee < ActiveRecord::Base
has_many :furloughs, :dependent => :destroy
def years_employed
(DateTime.now - hire_date).round / 365
end
def vacation_days
if years_employed < 1 && newbie
((12 - hire_date.month) * 0.8).ceil
elsif years_employed <= 6
10
elsif years_employed <= 16
years_employed + 4
else
20
end
end
def newbie
Time.now.year == hire_date.year
end
def sick_days
5.0
end
def vacation_days_used
self.furloughs.collect(&:vacation_duration).inject(0) { | memo, n | memo + n }
end
def sick_days_used
self.furloughs.collect(&:sick_duration).inject(0) { | memo, n | memo + n }
end
def remaining_vacation_days
vacation_days - vacation_days_used
end
def remaining_sick_days
sick_days - sick_days_used
end
end
On January 1st, vacation/sick_days and vacation/sick_days_used methods need to reset, and then I also need to add a rollover method like
def rollover
if some_method_determining_last_year(remaining_vacation_days) >= 2
2
else
remaining_vacation_days
end
end
that needs to add to the newly calculated total as well. I'd appreciate any thoughts on what how I should approach this.
A database solution would probably be the simplest way to go, but since you're already on a track of trying to store as little information as possible, I might have a solution for you. You already have a method for the number of vacation/sick days (probably really should be hours) an employee has. From there you can have a method that calculates the total vacation and sick days an employee has ever earned. Then it's just a simple calculation between the number of days they have used and the number of hours the have earned. You will probably have to add a filter to those methods for expired hours, but you get the idea. I also assume that vacation_days_used are days used for the entirety of their employment (can't tell since I can't see the furlough model):
def total_vacation_days_earned
# vacation_days has to be recalculate, probably should be a mapped function.
# for each year, how many vacation days did you earn, but this is a rough method
vacation_days * years_employed # can put cutoffs like, first 2 years employed, etc.
end
def total_sick_days_earned
sick_days * years_employed
end
def vacation_days_used
self.furloughs.collect(&:vacation_duration).inject(0) { | memo, n | memo + n }
end
def sick_days_used
self.furloughs.collect(&:sick_duration).inject(0) { | memo, n | memo + n }
end
def remaining_vacation_days
total_vacation_days_earned - vacation_days_used
end
def remaining_sick_days
total_sick_days_earned - sick_days_used
end
Now you shouldn't need a rollover method since at the beginning of the year each employee has a running total of time they have ever accrued minus a running total of time they have ever used.
Most employers will give you a fraction of the holidays you have accrued each payroll period, not upfront at the beginning of the year. You will probably eventually need to segment it out by payroll period as well as years_employed by payroll (i.e., Employee has been here 24 payroll cycles, or 2 years). You will need a more robust years_employed method, I've written up a gist that can be used as a starting point for a method that calculates years served as incremented calendar (not always 365 days) year from the hire_date. You could modify this to increment every two weeks, fifteen days, whatever the payroll cycle is. You could also add a service or additional model to handle payroll methods.
I would recommend extending the Employee model/schema to include two additional columns: remaining_vacation_days and sick_days.
Upon creation of Employee, set these values appropriately and decrement remaining_vacation_days and increment sick_days after a Furlough save (see after_save).
Finally, on Jan 1st use your vacation_days and sick_days methods to reset these values for the new year.

How to display output with two digits of precision

Here is my code
class Atm
attr_accessor :amount, :rem, :balance
TAX = 0.50
def transaction
#rem = #balance=2000.00
#amount = gets.chomp.to_f
if #amount%5 != 0 || #balance < #amount
"Incorrect Withdrawal Amount(not multiple of 5) or you don't have enough balance"
else
#rem = #balance-(#amount+TAX)
"Successful Transaction"
end
end
end
a=Atm.new
puts "Enter amount for transaction"
puts a.transaction
puts "Your balance is #{a.rem.to_f}"
and my output is
Enter amount for transaction
100 # user enters this value
Successful Transaction
Your balance is 1899.5
as you can see the output, 'Your balance is 1899.5' only displays one digit of precision. I need help to understand and fix the issue. I want two digits of precision in the output.
And also how can I improve this code?
You can use this:
puts "Your balance is #{'%.02f' % a.rem}"
But remember that this code will round your result if you have more than 2 decimal places. Ex.: 199.789 will become 199.79.
It's a fundamental design flaw to store money as a floating point number because floats are inexact. Money should always be stored as an integer in the smallest unit of currency.
Imagine two accounts with 1.005. Display them both, and suddenly there is an extra penny in the world.
Instead store the amount of money in an integer. For example, $1 would be balance = 100 or 100 pennies. Then format the displayed value:
money = 1000
"%.2f" % (money / 100.0)
# => 10.00
number_with_precision(value, :precision => 2)
Should work in Rails

Resources