Why isn't `"repeat" * 3` the same as `3 * "repeat"` in Ruby? - ruby

When I type this:
puts 'repeat' * 3
I get:
>> repeat repeat repeat
But it's not working if I do this:
puts 3 * 'repeat'
Why?

In Ruby, when you call a * b, you're actually calling a method called * on a. Try this, for example:
a = 5
=> 5
b = 6
=> 6
a.*(b)
=> 30
c = "hello"
=> "hello"
c.*(a)
=> "hellohellohellohellohello"
Thus <String> * <Fixnum> works fine, because the * method on String understands how to handle integers. It responds by concatenating a number of copies of itself together.
But when you do 3 * "repeat", it's invoking * on Fixnum with a String argument. That doesn't work, because Fixnum's * method expects to see another numeric type.

Related

Ruby, how to overwrite the setter ("=") method to allow 2 parameters?

I am trying to create a quick set method for my multi component variable (in my real code it is a Vector2d).
I would like to use an overwritten version of the = method.
For example:
def my_vector=(x, y)
#my_vector = Vector2d.new(x, y)
end
But it is not working because when I try to call this method I receive an error:
my_vector=1, 2 # => wrong number of arguments (given 1, expected 2)
my_vector=(1, 2) # => syntax error, unexpected ',', expecting ')'
my_vector=[1, 2] # => wrong number of arguments (given 1, expected 2)
This is my test suite:
# With one param it works
def my_var=(value)
#var = value
end
self.my_var=1
puts "var: " + #var.to_s
# With 2 params it doesn't work
def my_other_var=(value1, value2)
#other_var_component_1 = value1
#other_var_component_2 = value2
end
# Nothing works:
# self.my_other_var=1, 2
# self.my_other_var=(1, 2)
# self.my_other_var=[1, 2]
puts "other_var_component_1: " + #other_var_component_1.to_s
puts "other_var_component_2: " + #other_var_component_2.to_s
As #eugen correctly says, you cannot pass two arguments to a setter like
self.my_vector = 1, 2
The nearest thing you can achieve is to unpack the argument using pattern matching:
def myvector=(arg)
arg => [x, y]
#my_vector = Vector2d.new(x, y)
end
foo.my_vector = [1, 2]
An alternative is to define a simple helper method v so you can write
foo.my_vector = v 1, 2
You're defining a method named my_vector= (or my_other_var= in your 2nd example).
If you're just trying to call it with
my_vector = 1, 2
ruby will interpret that as an assignment to the my_vector variable.
You'll have to use send in order to call the method you've defined, as in
self.send :my_vector=, 1, 2
That's kind of ugly, but I don't see another way around this.
Slight modification to the supplied code to use an array argument instead:
def my_other_var=(values)
#other_var_component_1 = values[0]
#other_var_component_2 = values[1]
end
self.my_other_var=[1, 2]
"other_var_component_1: " + #other_var_component_1.to_s
#=> other_var_component_1: 1
"other_var_component_2: " + #other_var_component_2.to_s
#=> other_var_component_2: 2

I have Integers in an array that I need converted to strings

When I type
digicollect=[]
digicollect[0]=2
I get 2 when I type in digicollect in the irb.
Also, when I type in
"Hello" * 2
I get "HelloHello"
But if I type in
2 * "Hello"
it doesn't work.
"hello" * digicollect
doesn't work.
but
"hello" * digicollect[0]
does work.
Why?
Everything in ruby is an object, and even multiplications are just method calls.
"Hello" * 2 is the same as "Hello".*(2)
So when you get an error you should ask yourself: Do the left hand side really have the multiplication method and will it accept the right hand side as an argument?
digicollect = []
digicollect[0] = 2
Let us check what kind of objects we have:
p digicollect.class #=> Array
p digicollect[0].class #=> Fixnum
p 2.class #=> Fixnum
p "Hello".class #=> String
Now if we go into the docs for the *-method we find what each class expect:
http://ruby-doc.org/core-2.1.0/String.html#method-i-2A
http://ruby-doc.org/core-2.1.0/Array.html#method-i-2A
http://ruby-doc.org/core-2.1.0/Fixnum.html#method-i-2A
In there we find what will happen:
String expects an Integer. (num of times to repeat string)
Array expects an Integer or a String. (to repeat array x times or to join using string)
Fixnum expects an Numeric. (For simple multiplication)
Thus when you write "hello" * digicollect you are trying to call the multiplication method of a sting and pass it an Array, and the method simply do not know how to handle it (it will only accept Integers), that is why you get the error.
I should preface this by saying I don't get 2 when I type digicollect in irb ... I get [2]. This is a single element array with a value of 2. That's very different from the integer value 2.
String has no * operator for an array argument, and number has no * operator with a string argument. However, String does have * with a number argument, and digicollect[0] access the numeric value of that array element.
digicollect itself is not numeric, that's why you can't 'multiply' by it. It contains numbers, though, which is why "hello" * digicollect[0] works.
As for the 2 * "Hello" case, I believe that's a syntactic thing about the language - The string must come first and the integer second.

What does ":*" (colon-star) mean in Ruby?

Looking up how to calculate the factorial of a number I came across this code:
(1..5).inject(:*) || 1 # => 120
What is the (:*) || 1 doing?
How does it compare to this line of code (1..5).inject(1) { |x, y| x * y } # => 120, which uses .inject to achieve similar functionality?
Colon-star in itself doesn't mean anything in Ruby. It's just a symbol and you can pass a symbol to the inject method of an enumerable. That symbol names a method or operator to be used on the elements of the enumerable.
So e.g.:
(1..5).inject(:*) #=> 1 * 2 * 3 * 4 * 5 = 120
(1..5).inject(:+) #=> 1 + 2 + 3 + 4 + 5 = 15
The || 1 part means that if inject returns a falsey value, 1 is used instead. (Which in your example will never happen.)
test.rb:
def do_stuff(binary_function)
2.send(binary_function, 3)
end
p do_stuff(:+)
p do_stuff(:*)
$ ruby test.rb
5
6
If you pass a method name as a symbol, it can be called via send. This is what inject and friends are doing.
About the || part, in case the left hand side returns nil or false, lhs || 1 will return 1
It's absolutely equal. You may use each way, up to your taste.

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

Why can one multiply string with int with block as closure in Ruby?

While reading the book Programming Ruby, one example shows how blocks can be used as closure:
def nTimes(aThing)
return proc {|n| aThing * n}
end
p = nTimes("Hello ")
Now if we output the value of p.call(3) , it would be Hello Hello Hello
However, if our code was simply puts 3 * "Hello " , Ruby would complain about incompatible type.
Why? Thanks.
Your problem has nothing to do with closures or blocks. It is related to how operators are handled in Ruby.
On binary operations like * and +, the object to the left of the operand is the receiver of the method. So when you do "hello " * 3 it calls the * method on the class String and passes 3 as a parameter. The definition of String#* takes integers as parameters and returns self repeated that many times, hence the output "hello hello hello ".
But if you phrase it as 3 * "hello ", the * method of the Fixnum class is called, and "hello " is passed as a parameter. Fixnum#* doesn't know what to do with String parameters so you get an error.

Resources