Ruby overloading operators with severals parameters - ruby

I have a class MoneyBox with two fields (wallet and cent). I need to overload operations +, -, and * with these fields. How to correctly implement the overloading of these operators with two parameters?
class MoneyBox
attr_accessor :wallet, :cent
def initialize(wallet, cent)
#wallet = wallet
#cent = cent
end
def + (obj, obj_cent)
self.wallet = self.wallet + obj.wallet
self.cent = self.cent + obj_cent.cent
return self.wallet, self.cent
end
def - (obj, obj_cent)
self.wallet = self.wallet - obj.wallet
self.cent = self.cent - obj_cent.cent
return self.wallet, self.cent
end
def * (obj,obj_cent)
self.wallet = self.wallet * obj.wallet
self.cent = self.cent * obj_cent
return self.wallet, self.cent
end
end
It should be something like this:
Cash1 = MoneyBox.new(500, 30)
Cash2 = MoneyBox.new(100, 15)
puts " D1 = #{D1 = (Cash1-(Cash2*2))}" # 500,30 - 100,15*2 = 300,0

You already have a class that encapsulates wallet / cent pairs and the operations are also performed pair-wise. Why not take advantage of it and make +, - and * take a (single) MoneyBox instance as their argument and return the result as another MoneyBox instance, e.g.: (arithmetic operators shouldn't modify their operands)
class MoneyBox
attr_accessor :wallet, :cent
def initialize(wallet, cent)
#wallet = wallet
#cent = cent
end
def +(other)
MoneyBox.new(wallet + other.wallet, cent + other.cent)
end
def -(other)
MoneyBox.new(wallet - other.wallet, cent - other.cent)
end
def *(other)
MoneyBox.new(wallet * other.wallet, cent * other.cent)
end
def to_s
"#{wallet},#{cent}"
end
end
Example usage:
cash1 = MoneyBox.new(500, 30)
cash2 = MoneyBox.new(100, 15)
puts "#{cash1} + #{cash2} = #{cash1 + cash2}"
# 500,30 + 100,15 = 600,45
puts "#{cash1} - #{cash2} = #{cash1 - cash2}"
# 500,30 - 100,15 = 400,15
puts "#{cash1} * #{cash2} = #{cash1 * cash2}"
# 500,30 * 100,15 = 50000,45
To multiply both wallet and cents by 2 you'd use a MoneyBox.new(2, 2) instance:
puts "#{cash1} - #{cash2} * 2 = #{cash1 - cash2 * MoneyBox.new(2, 2)}"
# 500,30 - 100,15 * 2 = 300,0
Note that operator precedence still applies, so the result is evaluated as cash1 - (cash2 * Money.new(2, 2)).
If you want to multiply by integers directly, i.e. without explicitly creating that MoneyBox.new(2, 2) instance, you could move that logic into * by adding a conditional, e.g:
def *(other)
case other
when Integer
MoneyBox.new(wallet * other, cent * other)
when MoneyBox
MoneyBox.new(wallet * other.wallet, cent * other.cent)
else
raise ArgumentError, "expected Integer or MoneyBox, got #{other.class}"
end
end
Which gives you:
cash1 = MoneyBox.new(500, 30)
cash2 = MoneyBox.new(100, 15)
puts "#{cash1} - #{cash2} * 2 = #{cash1 - cash2 * 2}"
# 500,30 - 100,15 * 2 = 300,0
Note that this only defines MoneyBox#* and doesn't alter Integer#*, so 2 * cash2 does not work by default:
2 * cash2
# TypeError: MoneyBox can't be coerced into Integer
That's because you're calling * on 2 and Integer doesn't know how to deal with a MoneyBox instance. This could be fixed by implementing coerce in MoneyBox: (something that only works for numerics)
def coerce(other)
[self, other]
end
Which effectively turns 2 * cash2 into cash2 * 2.
BTW if you always call * with an integer and there's no need to actually multiply two MoneyBox instances, it would of course get much simpler:
def *(factor)
MoneyBox.new(wallet * factor, cent * factor)
end

You can't use the typical x + y syntax if you're going to overload the operator to take two arguments.
Instead, you have to use .+, see the following:
class Foo
def initialize(val)
#val = val
end
def +(a,b)
#val + a + b
end
end
foo = Foo.new(1)
foo.+(2,3)
# => 6

Your example usage does not match the method definition. You want to use your objects in the way to write expressions like i.e. Cash2 * 2). Since Cash2 is a constant of class MoneyBox, the definition for the multiplication by a scalar would be something like
def * (factor)
self.class.new(wallet, cent * factor)
end
This is a shortcut of
def * (factor)
MoneyBox.new(self.wallet, self.cent * factor)
end

Related

implementation of operator overload (+=) in ruby [duplicate]

This question already has an answer here:
+= operator overloading in ruby
(1 answer)
Closed 3 years ago.
how to implement operator overloading (operator += )for two one-dimensional arrays? for b+=a in method def +# ??? end
class Vector
def initialize
#vector = Array.new
end
def set_vector=(vector)
#vector = vector
end
def get_vector
#vector
end
def +#
?????
end
end
a = Vector.new
a.set_vector = [1,3,4,5]
print a.get_vector
b = Vector.new
b.set_vector = [1,2]
print b.get_vector
a.get_vector += b.get_vector
puts "\n a new"
puts a
There's a more Ruby way of doing this that fixes the issue:
class Vector
attr_accessor :vector
def initialize(*values)
#vector = values
end
def [](i)
#vector[i]
end
def +(vector)
Vector.new(
*#vector.map.with_index do |v, i|
v + vector[i].to_i
end
)
end
end
Note that + is just a regular method, there's no +# method involved here, that's the unary + as in r = +v, but you want to return a new object so you can chain it, as in a + b + c, and never modify the original.
+= creates a new object and does the assignment, as in x = x + 1.
In practice:
v = Vector.new(1,2)
r = v + Vector.new(2,3)
# => #<Vector:0x00007fc6678955a0 #vector=[3, 5]>

Placing a box around centered text in Ruby

This is a super basic ruby question. Just learning ruby and I'm trying to build out a box where it finds the longest word in the string and makes the box that width with spaces on either side and then centers all the other text i've passed in.
so far i have this;
def box(str)
arr = str.split
wordlength = arr.max_by(&:length).length
width = wordlength + 4
width.times{print "*"}
puts "\n"
arr.each {|i| puts "* #{i} *" }
width.times{print "*"}
end
but the above prints out:
***********
* texting *
* stuff *
* test *
* text *
***********
i'd like it to print something like the below
***********
* texting *
* stuff *
* test *
* text *
***********
thanks
Here, this code works:
def box(str)
arr = str.split
wordlength = arr.max_by(&:length).length
width = wordlength + 4
width.times{print "*"}
puts "\n"
arr.each do |i|
current_length = i.length
puts "* #{fill_space(current_length, wordlength, 'pre', i)}#{i}#{fill_space(current_length, wordlength, 'post')} *"
end
width.times{print "*"}
end
def fill_space(current_length, max_length, where, current='')
spaces_to_fill = max_length - current_length
if where == 'pre'
str = ' ' * (spaces_to_fill / 2)
elsif spaces_to_fill % 2 > 0
str = ' ' * (spaces_to_fill / 2 + 1)
else
str = ' ' * (spaces_to_fill / 2)
end
end
The problem was,that you didn't calculate how many " " you should insert into your current row. In the fill_space function I calculate exactly that, and I call this function for each row that should be printed. Also, if there are odd words this function adds the additional space in the end of the row.
I didn't change much of your box function but feel free to insert the hint that Keith gave you

Best way to store a formula with specific values to be evaluated later on

Working on a Ruby-based application that requires custom formulas to be created, stored and evaluated at a later date. For example, I might have:
10 * (2 + number_of_pies_eaten)
where number_of_pies_eaten is currently unknown, and will be substituted into the formula when it is evaluated.
Is there a best practices way to do this kind of thing beyond just writing my own interpolation interpreter?
I had a similar issue when I wanted to create a class to solve Riemann sum of a function.
In that case, I used Procs:
f = ->(x) { -10*(x**2) + 3*x + 6 }
I would say that it depends on your implementation. You could actually:
Define a class containing some formulas, passing your number_of_pies_eaten in the constructor and do something
class PieEater
def initialize(number_of_pies_eaten)
#eaten_pies = number_of_pies_eaten
end
def something
#something = 10 * (2 + #eaten_pies)
end
def something_else
#something_else = 5 * (3 + #eaten_pies)
end
end
Usage:
pe = PieEater.new(15)
pe.do_something
pe.do_something_else
Define a module containing some formulas to include into another class.
module PieEater
def something(x)
10 * (2 + x)
end
def something_else(x)
5 * (3 + x)
end
end
Usage:
require 'pie_eater'
class Person
include PieEater
end
p = Person.new
p.something(15)
Create a module or a class containing some formulas to use straight away.
module PieEater
extend self
def something(x)
10 * (2 + x)
end
def something_else(x)
5 * (3 + x)
end
end
or
class PieEater
class << self
def something(x)
10 * (2 + x)
end
def something_else(x)
5 * (3 + x)
end
end
end
Usage:
PieEater.something(15)
If you are writing some kind of script, I would use Proc objects:
$ irb
2.4.0 :001 > eaten_pies = ->(n) { 10 * (2 + n) }
=> #<Proc:0x00000001ff5c70#(irb):1 (lambda)>
2.4.0 :002 > eaten_pies.call(2)
=> 40
# You could even use these procs as hash values
2.4.0 :003 > formulas = { a: eaten_pies, b: ->(x){ x**2 } }
=> {:a=>#<Proc:0x00000001ff5c70#(irb):1 (lambda)>, :b=>#<Proc:0x00000002015070#(irb):4 (lambda)>}
2.4.0 :004 > formulas[:a].call(15)
=> 170
2.4.0 :005 > formulas[:b].call(15)
=> 225
This could be written as:
formulas = {
eaten_pies: ->(x) { 10 * (2 + n) },
eaten_apples: ->(y) { y ** 2 }
}
formulas[:eaten_pies].call(15)
All this possibilities are standards in Ruby, it depends on your use case and your coding style

Use Shopify Script Editor to Set Static Price by Tag

I've examined similar questions and solutions but I was not able to get them to work with mine. I need to have a bunch of products set to a static price of $50, there is no specific discount I can apply as the actual price on these all vary. Here is the code I have so far:
class StaticPrice
def initialize(selector)
#selector = selector
end
TagSelector
class TagSelector
def initialize(tag)
#tag = tag
end
def match?(line_item)
line_item.variant.product.tags.include?(#tag)
end
end
CAMPAIGNS = [
StaticPrice.new(
TagSelector.new("boots"),
line_item.line_price == (5000), message: "SALE!")
]
Output.cart = Input.cart
**** UPDATE... Well I got it to work, however it's extremely bloated and I'm quite sure unprofessional (rookie here), but.. it works.. This allows me to set static prices on products based off tags for a particular sale while at the same time not allowing someone to use a coupon to get any additional price off of the sale item.. I appreciate any suggestions for improvement ****
case Input.cart.discount_code
when CartDiscount::Percentage
if Line_items.quantity > 1
Input.cart.discount_code.reject(message: "Coupons can not be combined with BOGO promotion")
end
end
class ItemCampaign
def initialize(selector, discount, partitioner)
#selector = selector
#discount = discount
#partitioner = partitioner
end
def run(cart)
applicable_items = cart.line_items.select do |line_item|
#selector.match?(line_item)
end
discounted_items = #partitioner.partition(cart, applicable_items)
discounted_items.each do |line_item|
#discount.apply(line_item)
end
end
end
class TagSelector
def initialize(tag)
#tag = tag
end
def match?(line_item)
line_item.variant.product.tags.include?(#tag)
end
end
class PercentageDiscount50
def initialize(percent, message)
#percent = Money.new(cents: 100) * 50
#message = message
end
def apply(line_item)
line_discount = line_item.line_price - line_item.line_price + Money.new(cents: 100) * 50
new_line_price = Money.new(cents: 100) * 50
line_item.change_line_price(new_line_price, message: #message)
puts "Discounted line item with variant #{line_item.variant.id} by #{line_discount}."
end
end
class PercentageDiscount40
def initialize(percent, message)
#percent = Money.new(cents: 100) * 40
#message = message
end
def apply(line_item)
line_discount = line_item.line_price - line_item.line_price + Money.new(cents: 100) * 40
new_line_price = Money.new(cents: 100) * 40
line_item.change_line_price(new_line_price, message: #message)
puts "Discounted line item with variant #{line_item.variant.id} by #{line_discount}."
end
end
class PercentageDiscount30
def initialize(percent, message)
#percent = Money.new(cents: 100) * 30
#message = message
end
def apply(line_item)
line_discount = line_item.line_price - line_item.line_price + Money.new(cents: 100) * 30
new_line_price = Money.new(cents: 100) * 30
line_item.change_line_price(new_line_price, message: #message)
puts "Discounted line item with variant #{line_item.variant.id} by #{line_discount}."
end
end
class PercentageDiscount20
def initialize(percent, message)
#percent = Money.new(cents: 100) * 20
#message = message
end
def apply(line_item)
line_discount = line_item.line_price - line_item.line_price + Money.new(cents: 100) * 20
new_line_price = Money.new(cents: 100) * 20
line_item.change_line_price(new_line_price, message: #message)
puts "Discounted line item with variant #{line_item.variant.id} by #{line_discount}."
end
end
class PercentageDiscount10
def initialize(percent, message)
#percent = Money.new(cents: 100) * 10
#message = message
end
def apply(line_item)
line_discount = line_item.line_price - line_item.line_price + Money.new(cents: 100) * 10
new_line_price = Money.new(cents: 100) * 10
line_item.change_line_price(new_line_price, message: #message)
puts "Discounted line item with variant #{line_item.variant.id} by #{line_discount}."
end
end
class LowToHighPartitioner
def initialize(paid_item_count, discounted_item_count)
#paid_item_count = paid_item_count
#discounted_item_count = discounted_item_count
end
def partition(cart, applicable_line_items)
sorted_items = applicable_line_items.sort_by{|line_item| line_item.variant.price}
total_applicable_quantity = sorted_items.map(&:quantity).reduce(0, :+)
discounted_items_remaining = Integer(total_applicable_quantity / (#paid_item_count + #discounted_item_count) * #discounted_item_count)
discounted_items = []
sorted_items.each do |line_item|
break if discounted_items_remaining == 0
discounted_item = line_item
if line_item.quantity > discounted_items_remaining
discounted_item = line_item.split(take: discounted_items_remaining)
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 0, discounted_item)
end
discounted_items_remaining -= discounted_item.quantity
discounted_items.push(discounted_item)
end
discounted_items
end
end
CAMPAIGNS = [
ItemCampaign.new(
TagSelector.new("SCRIPT50"),
PercentageDiscount50.new(10, "$50 FINAL SALE!"),
LowToHighPartitioner.new(0,1),
),
ItemCampaign.new(
TagSelector.new("SCRIPT40"),
PercentageDiscount40.new(10, "$40 FINAL SALE!"),
LowToHighPartitioner.new(0,1),
),
ItemCampaign.new(
TagSelector.new("SCRIPT30"),
PercentageDiscount30.new(10, "$30 FINAL SALE!"),
LowToHighPartitioner.new(0,1),
),
ItemCampaign.new(
TagSelector.new("SCRIPT20"),
PercentageDiscount20.new(10, "$20 FINAL SALE!"),
LowToHighPartitioner.new(0,1),
),
ItemCampaign.new(
TagSelector.new("SCRIPT10"),
PercentageDiscount10.new(10, "$10 FINAL SALE!"),
LowToHighPartitioner.new(0,1),
)
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
When you are instantiating a new StaticPrice object, you are sending in 3 attributes but your object accepts only the one. You instantiate TagSelector, but you never use the match? method.
Without knowing more, seeing more, and having more of your explanations, the amount of code you are providing is of little use.
Why not just iterate the cart, and set prices. That is trivial and does not involve complex objects. Experiment with simpler code, and build up to a more organized approach as you gain confidence. There is little need for the campaign, StaticPrice and TagSelector till you actually have some working script code that exhibits a need for them.

Exponentiation not working

I'm new to programming, especially in Ruby so I've been making some basic projects. I have this code and as far as I know, it should work, but it gives results that I don't expect
The program takes a and B and returns a^b. I did this as a programming exercise, hence why I didn't just go a**b.
class Exponate
attr_accessor :args
def initialize args = {}
#args = args
#ans = nil
end
def index
#args[:b].times {
#ans = #args[:a] * #args[:a]
}
puts #ans
end
end
e = Exponate.new(:a => 32, :b => 6)
e.index
e.args[:a] = 5
e.index
Returns
1024 # Should be 1_073_741_824
25 # Should be 15_625
But they are definitely not that
You can write like this:
class Exponate
attr_accessor :args, :ans
def initialize args = {}
#args = args
end
def index
#ans = 1 # multiplication will start from 1
#args[:b].times {
#ans *= #args[:a] #same as #ans = #ans * #args[:a]
}
puts #ans
end
end
#ans = #args[:a] * #args[:a] will return the same value, no matter how many times called, you need to reference the accumulator variable in some way to make use of the cycle.
Using an instance variable for a local does not seem right - their lifetime is longer, so after method exits they cannot not be collected if the whole object is still referenced somewhere. Also the #s are more error-prone - if you make a typo (for example - #asn instead of #ans), you'll get nil instead of NameError, it may be harder to debug, so better to write this way:
def index
ans = 1
args[:b].times {
ans *= args[:a]
}
puts ans
end
For loops with an accumulator in ruby it's better to use Enumerable#inject:
#ans = #args[:b].times.inject(1){|acc,v| acc * #args[:a]}
this way it's less likely to forget initialisation.

Resources