Can I implement += in my class to increase a contained value? [duplicate] - ruby

This question already has answers here:
Ruby method for +=
(2 answers)
Closed 9 years ago.
Consider
class Container
def initialize(value = 0)
#value = value
end
def + (other)
return #value + other
end
def - (other)
return #value - other
end
def * (other)
return #value * other
end
def / (other)
return #value / other
end
def get
return #value
end
end
I would like to use += to increase the value in the container, like this:
c = Container.new(100)
c += 100
print c.get # Expecting 200
The above won't work, as 100 will overwrite c.
I know I could use something like an attr_accessor to generate a getter and setter for the value, but I'm curious if I could do this in a prettier way such as using +=.

Since c += 100 is just a sugar for c = c + 100, you can't escape overwriting c. BUT you can overwrite it with a similar object (and not with fixnum, as in your question).
class Container
def initialize(value = 0)
#value = value
end
def + (other)
Container.new(#value + other)
end
def get
#value
end
end
c = Container.new(100)
c += 100
c.get # => 200

x += y is just syntactic sugar for x = x + y. So you only have to implement + in your class and you get += for free.

No, you can't overload +=. See list of ruby operators that can be overridden/implemented for the full list of overloadable operators.
In Ruby x += y always means x = x + y. The only way to change the meaning of += for a given x is overriding + in x.class. However, + has a different semantics, and the user most probably expects that + returns a new object. If you make + return the original x, that may confuse some of your users. If you make + return a different object, then x would point to that other object in your example, and as far as I understand your question you don't want that.

Related

Ruby: how to overload arithmetic operations between numeric types and user-defined types

In Ruby the expression 1 + Complex(1,1) yields a Complex.
I would like to achieve the same for a user-defined type, so that (1 + MyType.new(...)) yields a value of MyType
Similarly for (1 - MyType.new(...)), but subtraction is not commutative.
Is this possible? How?
You can use coercion to handle this like so:
class MyType
attr_reader :value
def initialize(value)
#value = value
end
def +(n)
if n.is_a?(MyType)
MyType.new(self.value + n.value)
else
#value += n
self
end
end
def coerce(other)
[MyType.new(other), self]
end
end
1 + MyType.new(12)
#=> #<MyType:0x0000561609d5d9a8 #r=13>
# or even
1 + MyType.new(12) + 3
#=> #<MyType:0x0000561bc671d170 #r=16>
When the Integer 1 receives MyType as an argument to the + method it does not know how to handle it directly so it will try and coerce it to something it can understand by calling coerce on the argument object and passing itself to the coerce method.
The coerce method should always return an Array containing 2 values. Integer will then try and + those together using the first as the receiver and the second as the argument.
So essentially something like this:
a = 1
b = MyType.new(12)
begin
a + b
rescue TypeError
b.coerce(a).reduce(&:+)
end
This article offers a far more in-depth explanation of how the coercion works along with examples of when this would be useful.

Ruby - Finding primes under 100?

I'd like to write a code that prints out all primes under 100. Here is the code I have so far
class Numbers
def is_a_prime?(int)
x = 2
while x < int/2
if int % x == 0
return false
else
return true
end
end
end
def primes_under_100
x = 2
while x < 100
print x if is_a_prime?(x) # calling the method I defined above
x+= 1
end
end
end
Unfortunately when I call the method using primes_under_100 I get
undefined local variable or method 'primes_under_100' for main:Object
I'd like to know where I went wrong. None of my methods are private. Help is appreciated.
An other way to do this is extend Fixnum. Whit this you should be able to call it on int values.
this should be something like this
class Fixnum
def is_a_prime?
(2..(self/2)).each do |x|
if self % x == 0
return false
end
end
return true
end
end
In order for your code to work you will need to make the following modifications
class Numbers
def is_a_prime?(int)
x = 2
while x < int/2
if int % x == 0
return false
else
return true
end
end
end
def primes_under_100
x = 2
while x < 100
# Notice you're calling is_a_prime? on the instance of the Numbers object
# and sending x as an argument. Not calling is_a_prime? on the 'x'
print x if is_a_prime?(x)
x+= 1
end
end
end
Then call Numbers.new.primes_under_100
How are you calling it? They are public methods of the Number class, so in order to call them, you need to instantiate an object of the Number class:
number = Numbers.new
primes = number.primes_under_100
Also, as the comment from Leo Correa in my answer stated, the method is_a_prime? can't be called like that, you should use:
print x if is_a_prime?(x)
I don't know which version of Ruby include this method to Prime, but if you are using 2.2 and higher you can do it like this.
Add this to top of the file
require 'prime'
And method for showing primes under specific number.
Prime.each(100) do |prime|
p prime #=> 2, 3, 5, 7, 11, ...., 97
end
Here is the reference

Monte Carlo Simulation in Ruby

Hey I have a problem with my simulation.
I am a Ruby-Starter and don't know what's wrong in my code. That is only the part with the simulation:
def mean
mean = self.reduce(:+)/self.length.to_f
return mean
end
def randn
begin
rg1 = (rand*2)-1
rg2 = (rand*2)-1
q = rg1**2 + rg2**2
end while (q == 0 || q > 1)
p = Math.sqrt((-2*Math.log(q))/q)
rn1 = rg1 * p
rn2 = rg2 * p
return rn1, rn2
end
monte_carlo = 10
ren1_sim = Array.new
ren2_sim = Array.new
monte_carlo.times {
(1..20).each{ |i|
(1..250).each { |j|
r = randn()
ren1= * Math.exp(mu_ren1 + sigma_ren1 * r[0])
# ren1 is an array with prices, mu_ren1 and sigma_ren1 are individual values
ren2= * Math.exp(mu_ren2 + chol_21 * r[0] + chol_22 * r[1])
# chol_21 and chol_22 are also individual values
ren1_sim.push(ren1)
ren2_sim.push(ren2)
}
}
}
puts ren1_sim.mean
puts ren2_sim.mean
I don't get an error without the last two puts, but when I want to calculate the average of the arrays ren1_sim and rent_sim I get the error:
undefined method 'mean' for #<Array:0x2acf318> (NoMethodError)
Do you know how to fix that?
You're trying to invoke mean on an Array, which is not a method of Array. Perhaps you meant to use Statsample::Vector, which is Statsample's extension of Array, and does have mean?
ren1_sim = Statsample::Vector.new
ren2_sim = Statsample::Vector.new
You can also call to_vector on an Array instance to get a Statsample::Vector.
You've defined a mean method at the top of your file, but that just creates a method on the top level object, and you're trying to call it on an individual array. You could either change that code to
def mean(array)
array.reduce(:+)/array.length.to_f
end
and then change your usage of it later on to mean(ren1_sim)
or change your code so that you are adding the method to array, i.e.
class Array
def mean
self.reduce(:+)/self.length.to_f
end
end
have a look at this post to calculate the average of a array
How do I create an average from a Ruby array?

undefined method ... for class (NoMethodError)

I'm just startin to learn ruby and I'm writing a simple program, but I've got an error undefined method 'send_for_beer' for Person:Class (NoMethodError)
Here is a code:
class Person
#iq = 0
#speed = 0
#power = 0
#beauty = 0
def initialize (iq, speed, power, beauty)
#iq = iq
#speed = speed
#power = power
end
def send_for_beer
result #iq * 2 + #speed * 10 + #power * 5 + #beauty
return result
end
end
number_of_people = 3
person_array = Array.new(number_of_people, Person)
n = 0
beer_person = 0
beer_cof = 0
number_of_people.times do
............
person_array.push(Person.new(iq, speed, power, beauty))
if person_array[n].send_for_beer > beer_cof <-----here is an error
beer_cof = person_array[n].send_for_beer
beer_person = n
end
n = n+1
end
Here's your problem:
person_array = Array.new(number_of_people, Person)
In short, don't make array like this. Use the [] literal syntax. What this returns is:
[Person, Person, Person]
That is 3 references to the Person class, not instances. Then later you do:
person_array.push(Person.new(iq, speed, power, beauty))
And you end up with:
[Person, Person, Person, person_instance]
So when you iterate through and call send_for_beer on that first item, it does have that method because send_for_beer is an instance method that you are calling erroneously on a class object.
The fix here is to simply assign person_array to an empty array literal, and then push things to it.
person_array = []
And a minor style note: << is usually preferred to Array#push, making the filling of the array look more like this.
person_array << Person.new(iq, speed, power, beauty)
Ruby also support implicit return of the last expression in a method. So you do not need to return result. Instead, simply calulate the return value as the only line in the method.
def send_for_beer
#iq * 2 + #speed * 10 + #power * 5 + #beauty
end
Instance variables don't quite work like that either. When you have #name in the class body directly, you are not initializing instance variables for each instance. You are actually setting instance variable on the class object (which is weird, I know). What you actually need to do is set them from any instance method, typically initialize, which you are doing here. So you can totally remove the instance variable setting at the class level here.
I think you've a syntax error in the method send_for_beer , the = sign is missing in the affectation of the variable result.
By the way, the method can be written
def send_for_beer
#iq * 2 + #speed * 10 + #power * 5 + #beauty
end
If you have an array of fixed length, you can supply a block to create a new Person object for each element. You could rewrite your person_array line as follows:
person_array = Array.new(number_of_people) { Person.new(0, 0, 0, 0) }
Add the following line to the top of your class.
attr_writer(:iq, :speed, :power, :beauty)
This snipped of code could then modify the objects in your array.
person_array.each do |p|
p.iq, p.speed, p.power, p.beauty = rand(20) + 1, rand(5) + 1, 1
p.beauty = 10 if (rand(2) == 0)
end

ruby operator overloading question

i've been messing around with ruby and opengl for entertainment purposes, and i decided to write some 3d vector/plane/etc classes to pretty up some of the math.
simplified example:
class Vec3
attr_accessor :x,:y,:z
def *(a)
if a.is_a?(Numeric) #multiply by scalar
return Vec3.new(#x*a, #y*a, #z*a)
elsif a.is_a?(Vec3) #dot product
return #x*a.x + #y*a.y + #z*a.z
end
end
end
v1 = Vec3.new(1,1,1)
v2 = v1*5 #produces [5,5,5]
which all fine and dandy, but i also want to be able to write
v2 = 5*v1
which requires adding functionality to Fixnum or Float or whatever, but i couldn't find a way to overload or extend fixnum's multiplication without replacing it entirely. is this possible in ruby? any tips?
(obviously i can just write all my multiplications in the correct order if i need to)
Using coerce is a MUCH better approach than monkey-patching a core class:
class Vec3
attr_accessor :x,:y,:z
def *(a)
if a.is_a?(Numeric) #multiply by scalar
return Vec3.new(#x*a, #y*a, #z*a)
elsif a.is_a?(Vec3) #dot product
return #x*a.x + #y*a.y + #z*a.z
end
end
def coerce(other)
return self, other
end
end
if you define v as v = Vec3.new then the following will work: v * 5 and 5 * v
The first element returned by coerce (self) becomes the new receiver for the operation, and the second element (other) becomes the parameter, so 5 * v is exactly equivalent to v * 5
I believe the following will do what you want, though banister's suggestion to use coerce instead of monkey-patching Numeric is a preferred method. Use this method only if necessary (for example if you only want some binary operands to be transitive).
Fixnum.class_eval do
original_times = instance_method(:*)
define_method(:*) do |other|
if other.kind_of?(Vec3)
return other * self
else
return original_times.bind(self).call(other)
end
end
end

Resources