This code invokes Array::[] with 1, 2 and 3 as arguments:
Array[1, 2, 3] #=> [1, 2, 3]
But this doesn't seem to call Array::[]:
[1, 2, 3] #=> [1, 2, 3]
So, which method is invoked by [...] in Ruby?
Motivation: I'm trying to stub the method in a test.
This is literal syntax for an array. It's not a message send. Ruby, like the vast majority of other languages, doesn't allow overloading of literals.
If you need literal overloading, you should use a language which does support it, such as Ioke.
Related
This question already has answers here:
What does map(&:name) mean in Ruby?
(17 answers)
Closed 2 years ago.
I done a coding challenge in Ruby not too long ago and wanted a better understanding of how the syntax below works, particularly with the last part of the expression (&:first).
def remove_every_other(arr)
arr.each_slice(2).map(&:first)
end
For some background the task was to take an array and remove every second element from the array.
Tests:
Test.assert_equals(remove_every_other(['Hello', 'Goodbye', 'Hello Again']), #=> ['Hello', 'Hello Again'])
Test.assert_equals(remove_every_other([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), #=> [1, 3, 5, 7, 9])
Test.assert_equals(remove_every_other([[1, 2]]), #=> [[1, 2]])
Test.assert_equals(remove_every_other([['Goodbye'], {'Great': 'Job'}]), #=> [['Goodbye']])
Test.assert_equals(remove_every_other([]), [])
& is a reference to a method.
:first is a symbol.
So &:first is simply a reference to the method named first in any object.
So this is the same thing as saying arr.each_slice(2).map {|it| it.first }.
When you map a method to elements of a collection, Ruby will simply call that method on the elements of the collection. In your case, arr.each_slice(2) will return elements of arr two by two (see the doc for each_slice), so if your array is e.g. [1, 2, 3, 4, 5, 6, 7, 8] it is a collection containing [[1, 2], [3, 4], [5, 6], [7, 8]]. Mapping :first on that collection means calling the first method on each element, and [1, 2].first simply returns 1 while [7, 8].first returns 7.
As the map method returns a collection with each element replaced with the result of the call to the method, you'll find yourself with a collection containing the first of each pair. This is how it removes every other element.
Note that if the original collection has an odd number of elements then the last one will be an array of one instead of two (see doc for each_slice), so if the array is [1, 2, 3] the slices are [[1, 2], [3]] and calling .first on each will result in [1, 3].
I found that the expression [*1..4] returns the same as if I would do a (1..4).to_a, but I don't understand the syntax here. My understanding is that * is - being a unary operator in this case - to be the splat operator, and to the right of it, we have a Range. However, if just write the expression *1..4, this is a syntax error, and *(1..4) is a syntax error too. Why does the first [*1..4] work and how it is understood in detail?
The splat * converts the object to an list of values (usually an argument list) by calling its to_a method, so *1..4 is equivalent to:
1, 2, 3, 4
On its own, the above isn't valid. But wrapped within square brackets, [*1..4] becomes:
[1, 2, 3, 4]
Which is valid.
You could also write a = *1..4 which is equivalent to:
a = 1, 2, 3, 4
#=> [1, 2, 3, 4]
Here, the list of values becomes an array due to Ruby's implicit array assignment.
I'm currently working on a simple multiplying method.
CODE:
def multiply(*numbers)
product = 1
numbers.each{|number|
product *= number
}
return product
end
puts multiply([2, 3, 4, 5])
OUTPUT:
*': Array can't be coerced into Fixnum (TypeError)
from calculator.rb:26:inblock in multiply'
from calculator.rb:24:in each'
from calculator.rb:24:inmultiply'
from calculator.rb:31:in `'
I get this error. It seems the method isn't allowing me to use ".each" on the array.
Also, I want to keep the parameter as *numbers in case it's not an array but two numbers to multiply. I should bring it to your attention that the method works fine when the parameter being passed are two numbers and not an array (i.e. multiply(2, 4)
multiply expects an arbitrary number of parameters. You pass only one parameter, which is an array. On the first iteration, number is the whole array. Hence the error message.
You have to fix the call, either
multiply(*[2, 3, 4, 5])
or, simpler,
multiply(2, 3, 4, 5)
The problem is different. In numbers.each you iterate over list of arguments, however with multiply([2, 3, 4, 5]) you are passing one argument, which is an array. The list of arguments is [[2, 3, 4, 5]]. So in the only iteration, you are trying to do:
1 *= [2, 3, 4, 5]
This basically makes no sense to Ruby, so it throws an error.
You should call multiply method with a list of arguments, not ona array argument:
multiply(2, 3, 4, 5)
Then it will work.
If you want to be able to use array as input, you can use to flatten method on the numbers array.
def multiply(*numbers)
product = 1
numbers.flatten.each{|number|
product *= number
}
return product
end
puts multiply([2, 3, 4, 5]) #=> 120
The flatten method will take a 2d array and turn into a simple array.
This will also work
multiply([2, 3, 4, 5], 2, 2) #=> 480
In this case,
numbers = [[2, 3, 4, 5], 2, 2]
After you apply the flatten method, you get
[2, 3, 4, 5, 2, 2]
Then you can begin to multiply each number.
As it is normal in Ruby, there is usually a method that do what you are looking to do. In this case, inject, and reduce can accomplish what you are looking for.
def multiply(*numbers)
numbers.flatten.reduce(&:*)
end
Nevermind! I realized that when passing an array using *args, it becomes and array of arrays.
I like how in Ruby you can pass methods as blocks like so using Symbol#to_proc:
[1.0, 2.0, 3.0].map(&:to_i)
#=> [1, 2, 3]
I can also define my own lambda, times_two, and pass it as a block as well:
times_two = ->(x) {x * 2}
[1, 2, 3].map(×_two)
#=> [2, 4, 6]
Though I seemingly cannot pass times_two as a symbol:
[1, 2, 3].map(&:times_two)
#=> ArgumentError: wrong number of arguments (0 for 1)
However when I try to do the same with a method I get an error:
def times_three(x)
x * 3
end
[1, 2, 3].map(×_three)
#=> ArgumentError: wrong number of arguments (0 for 1)
[1, 2, 3].map(&:times_three)
#=> ArgumentError: wrong number of arguments (0 for 1)
I'm guessing I can't do this because times_three is a method, not a Proc.
So how can you define custom methods so that they can be used in the Symbol#to_proc fashion like to_i in the first example above?
For example, how can I do this?
[1, 2, 3].map(&:times_three)
#=> [3, 6, 9]
EDIT:
I watched the video posted below and apparently you can get close to Symbol#to_proc using the method method:
def times_three(x)
x * 3
end
t_three = method(:times_three)
[1, 2, 3].map(&t_three)
#=> [3, 6, 9]
However, it's not quite Symbol#to_proc:
[1, 2, 3].map(&:t_three)
#=> NoMethodError: undefined method `t_three' for 1:FixNum
class Integer
def times_three
return self * 3
end
end
Now, because times_three is now a method of the Integer class, you can do symbol to proc...
[1, 2, 3].map(&:times_three)
If you want to access a method that isn't part of the object's class but acts on an object, you need to pass the object as an argument to the method...
def times_three(x)
x * 3
end
[1, 2, 3].map{|i| times_three(i) }
the symbol to proc needs to use the object as a receiver.
[1, 2, 3].map(&:some_action)
is equivalent to
[1, 2, 3].map{|i| i.some_action}
You would have to define times_three on Integer or Numeric.
Symbol to Proc explained by Peter Cooper: https://www.youtube.com/watch?v=aISNtCAZlMg
I often want to compare arrays and make sure that they contain the same elements, in any order. Is there a concise way to do this in RSpec?
Here are methods that aren't acceptable:
#to_set
For example:
expect(array.to_set).to eq another_array.to_set
or
array.to_set.should == another_array.to_set
This fails when the arrays contain duplicate items.
#sort
For example:
expect(array.sort).to eq another_array.sort
or
array.sort.should == another_array.sort
This fails when the arrays elements don't implement #<=>
Try array.should =~ another_array
The best documentation on this I can find is the code itself, which is here.
Since RSpec 2.11 you can also use match_array.
array.should match_array(another_array)
Which could be more readable in some cases.
[1, 2, 3].should =~ [2, 3, 1]
# vs
[1, 2, 3].should match_array([2, 3, 1])
I've found =~ to be unpredictable and it has failed for no apparent reason. Past 2.14, you should probably use
expect([1, 2, 3]).to match_array([2, 3, 1])
Use match_array, which takes another array as an argument, or contain_exactly, which takes each element as a separate argument, and is sometimes useful for readability. (docs)
Examples:
expect([1, 2, 3]).to match_array [3, 2, 1]
or
expect([1, 2, 3]).to contain_exactly 3, 2, 1
For RSpec 3 use contain_exactly:
See https://relishapp.com/rspec/rspec-expectations/v/3-2/docs/built-in-matchers/contain-exactly-matcher for details, but here's an extract:
The contain_exactly matcher provides a way to test arrays against each other in a way
that disregards differences in the ordering between the actual and expected array.
For example:
expect([1, 2, 3]).to contain_exactly(2, 3, 1) # pass
expect([:a, :c, :b]).to contain_exactly(:a, :c ) # fail
As others have pointed out, if you want to assert the opposite, that the arrays should match both contents and order, then use eq, ie.:
expect([1, 2, 3]).to eq([1, 2, 3]) # pass
expect([1, 2, 3]).to eq([2, 3, 1]) # fail
not documented very well but i added links anyways:
Rspec3 docs
expect(actual).to eq(expected)
Rspec2 docs
expect([1, 2, 3]).to match_array([2, 3, 1])