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])
Related
I'm pretty good at getting answers from google, but I just don't get this. In the following code, why does variable 'b' get changed after calling 'addup'? I think I understand why 'a' gets changed (although its a bit fuzzy), but I want to save the original array 'a' into 'b', run the method on 'a' so I have two arrays with different content. What am I doing wrong?
Thanks in advance
def addup(arr)
i=0
while i< arr.length
if arr[i]>3
arr.delete_at(i)
end
i += 1
end
return arr
end
a = [1,2,3,4]
b = a
puts "a=#{a}" # => [1,2,3,4]
puts "b=#{b}" # => [1,2,3,4]
puts "addup=#{addup(a)}" # => [1,2,3]
puts "a=#{a}" # => [1,2,3]
puts "b=#{b}" # => [1,2,3]
Both a and b hold a reference to the same array object in memory. In order to save the original array in b, you'd need to copy the array.
a = [1,2,3,4] # => [1, 2, 3, 4]
b = a # => [1, 2, 3, 4]
c = a.dup # => [1, 2, 3, 4]
a.push 5 # => [1, 2, 3, 4, 5]
a # => [1, 2, 3, 4, 5]
b # => [1, 2, 3, 4, 5]
c # => [1, 2, 3, 4]
For more information on why this is happening, read Is Ruby pass by reference or by value?
but I want to save the original array 'a' into 'b'
You are not saving the original array into b. Value of a is a reference to an array. You are copying a reference, which still points to the same array. No matter which reference you use to mutate the array, the changes will be visible through both references, because, again, they point to the same array.
To get a copy of the array, you have to explicitly do that. For shallow arrays with primitive values, simple a.dup will suffice. For structures which are nested or contain references to complex objects, you likely need a deep copy. Something like this:
b = Marhal.load(Marshal.dump(a))
In the following code, why does variable 'b' get changed after calling 'addup'?
The variable doesn't get changed. It still references the exact same array it did before.
There are only two ways to change a variable in Ruby:
Assignment (foo = :bar)
Reflection (Binding#local_variable_set, Object#instance_variable_set, Module#class_variable_set, Module#const_set)
Neither of those is used here.
I think I understand why 'a' gets changed (although its a bit fuzzy)
a doesn't get changed either. It also still references the exact same array it did before. (Which, incidentally, is the same array that b references.)
The only thing which does change is the internal state of the array that is referenced by both a and b. So, if you really understand why the array referenced by a changes, then you also understand why the array referenced by b changes, since it is the same array. There is only one array in your code.
The immediate problem with your code is that, if you want a copy of the array, then you need to actually make a copy of the array. That's what Object#dup and Object#clone are for:
b = a.clone
Will fix your code.
BUT!
There are some other problems in your code. The main problem is mutation. If at all possible, you should avoid mutation (and side-effects in general, of which mutation is only one example) as much as possible and only use it when you really, REALLY have to. In particular, you should never mutate objects you don't own, and this means you should never mutate objects that were passed to you as arguments.
However, in your addup method, you mutate the array that is passed to you as arr. Mutation is the source of your problem, if you didn't mutate arr but instead returned a new array with the modifications you want, then you wouldn't have the problem in the first place. One way of not mutating the argument would be to move the cloneing into the method, but there is an even better way.
Another problem with your code is that you are using a loop. In Ruby, there is almost never a situation where a loop is the best solution. In fact, I would go so far as to argue that if you are using a loop, you are doing it wrong.
Loops are error-prone, hard to understand, hard to get right, and they depend on side-effects. A loop cannot work without side-effects, yet, we just said we want to avoid side-effects!
Case in point: your loop contains a serious bug. If I pass [1, 2, 3, 4, 5], the result will be [1, 2, 3, 5]. Why? Because of mutation and manual looping:
In the fourth iteration of the loop, at the beginning, the array looks like this:
[1, 2, 3, 4, 5]
# ↑
# i
After the call to delete_at(i), the array looks like this:
[1, 2, 3, 5]
# ↑
# i
Now, you increment i, so the situation looks like this:
[1, 2, 3, 5]
# ↑
# i
i is now greater than the length of the array, ergo, the loop ends, and the 5 never gets removed.
What you really want, is this:
def addup(arr)
arr.reject {|el| el > 3 }
end
a = [1, 2, 3, 4, 5]
b = a
puts "a=#{a}" # => [1, 2, 3, 4, 5]
puts "b=#{b}" # => [1, 2, 3, 4, 5]
puts "addup=#{addup(a)}" # => [1, 2, 3]
puts "a=#{a}" # => [1, 2, 3, 4, 5]
puts "b=#{b}" # => [1, 2, 3, 4, 5]
As you can see, nothing was mutated. addup simply returns the new array with the modifications you want. If you want to refer to that array later, you can assign it to a variable:
c = addup(a)
There is no need to manually fiddle with loop indices. There is no need to copy or clone anything. There is no "spooky action at a distance", as Albert Einstein called it. We fixed two bugs and removed 7 lines of code, simply by
avoiding mutation
avoiding loops
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 have a Set of elements from which I want to take and remove the first few elements a bunch of times. Is there a shorter way (so one operation instead of two) to do that than this:
require 'set'
s = Set[1, 2, 3, 4] # => #<Set: {1, 2, 3, 4}>
first_two = s.take(2) # => [1, 2]
s.subtract(first_two) # => #<Set: {3, 4}>
(So basically I'm wondering whether I'm overlooking a shift for Sets)
You could add a new method take! (or remove! or whatever name seems appropriate) to the Set class:
class Set
def take!(args)
taken = self.take(args)
self.subtract(taken)
return taken
end
end
a = Set[1, 2, 3, 4] # <Set: {1, 2, 3, 4}>
a.take!(2) # <Set: {1, 2}>
a # <Set: {3, 4}>
There is no shorter way using builtin methods.
There is an open feature request for a method to return and remove one element; you may want to help refine the API?
From http://www.ruby-doc.org/stdlib-1.9.3/libdoc/set/rdoc/Set.html:
Set implements a collection of unordered values with no duplicates.
This is a hybrid of Array's intuitive inter-operation facilities and
Hash's fast lookup.
It would be odd and probably illogical to implement methods like shift and pop on an object that knows nothing about index.
I'm late to the party, but here's my solution. Convert the set to an array first, and then all Enumerable methods are available. Take 2 from the array and then be sure to also remove the from the set. The two values from the set get removed and returned.
require 'set'
s = Set[1, 2, 3, 4] # => #<Set: {1, 2, 3, 4}>
first_two = s.to_a.take(2).tap {|a| s.subtract(a)} # => [1, 2]
s # => #<Set: {3, 4}>
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.
One of the things I commonly get hooked up on in ruby is recursion patterns. For example, suppose I have an array, and that may contain arrays as elements to an unlimited depth. So, for example:
my_array = [1, [2, 3, [4, 5, [6, 7]]]]
I'd like to create a method which can flatten the array into [1, 2, 3, 4, 5, 6, 7].
I'm aware that .flatten would do the job, but this problem is meant as an example of recursion issues I regularly run into - and as such I'm trying to find a more reusable solution.
In short - I'm guessing there's a standard pattern for this sort of thing, but I can't come up with anything particularly elegant. Any ideas appreciated
Recursion is a method, it does not depend on the language. You write the algorithm with two kind of cases in mind: the ones that call the function again (recursion cases) and the ones that break it (base cases). For example, to do a recursive flatten in Ruby:
class Array
def deep_flatten
flat_map do |item|
if item.is_a?(Array)
item.deep_flatten
else
[item]
end
end
end
end
[[[1]], [2, 3], [4, 5, [[6]], 7]].deep_flatten
#=> [1, 2, 3, 4, 5, 6, 7]
Does this help? anyway, a useful pattern shown here is that when you are using recusion on arrays, you usually need flat_map (the functional alternative to each + concat/push).
Well, if you know a bit of C , you just have to visit the docs and click the ruby function to get the C source and it is all there..
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-flatten
And for this case, here is a Ruby implementation
def flatten values, level=-1
flat = []
values.each do |value|
if level != 0 && value.kind_of?(Array)
flat.concat(flatten(value, level-1))
else
flat << value
end
end
flat
end
p flatten [1, [2, 3, [4, 5, [6, 7]]]]
#=> [1, 2, 3, 4, 5, 6, 7]
Here's an example of a flatten that's written in a tail recursive style.
class Array
# Monkeypatching the flatten class
def flatten(new_arr = [])
self.each do |el|
if el.is_a?(Array)
el.flatten(new_arr)
else
new_arr << el
end
end
new_arr
end
end
p flatten [1, [2, 3, [4, 5, [6, 7]]]]
#=> [1, 2, 3, 4, 5, 6, 7]
ruby
Although it looks like ruby isn't always optimized for tail recursion: Does ruby perform tail call optimization?