How do you narrow down a series of arrays?
I have several arrays. And I want to narrow them down, so that at the end, whatever comes out, are values that are seen in all of the other arrays so long as those arrays are not nil.
My sad attempt at coding:
array_of_users = []
array_of_users & @zip_ids if !@zip_ids.empty?
array_of_users & @sex_ids if !@sex_ids.empty?
array_of_users & @interest_ids if !@interest_ids.empty?
array_of_users & @age_ids if !@age_ids.empty?
Logically, I would think this would work because its finding the similarity in each one so long as its not empty, but it doesn't actually add the开发者_StackOverflowm to the array.
How would you accomplish this?
I think you want this:
users = [@zip_ids,@sex_ids,@interest_ids,@age_ids].reject(&:empty?).reduce(&:&)
Think about it this way:
([1] | [2] | [3,4]) & [3,4]
=> [3,4]
So you could do:
array_of_users = @zip_ids | @sex_ids | @interest_ids | @age_ids
intersection = array_of_users & @zip_ids & @sex_ids & @interest_ids & @age_ids
Only, like @glenn says, it ignores the "dont merge if empty" requirement.
@DigitalRoss is good, but if the first array is empty, it all goes flat.
Favorite, then, from @glenn:
[[],[1,2],[2],[2,3,4,4],[]].reject(&:empty?).reduce(&:&)
=> [2]
You need to actually assign them to the array. At the point your code is sitting at, you're doing the intersection, but you're not actually assigning them to the array. You need to add ='s operators in there.
Here is an example setup:
>> x = [ 1, 1, 3, 5 ]
>> y = [ 1, 2, 3 ]
This is what you're doing right now:
>> x & y
=> [1, 3]
>> x
=> [1, 1, 3, 5]
This is where it needs to go:
>> x &= y
=> [1, 3]
>> x
=> [1, 3]
Thus, this should get the job done:
array_of_users = []
array_of_users &= @zip_ids if !@zip_ids.empty?
array_of_users &= @sex_ids if !@sex_ids.empty?
array_of_users &= @interest_ids if !@interest_ids.empty?
array_of_users &= @age_ids if !@age_ids.empty?
Hope this helps- Sidenote: I did all of this in IRB (interactive ruby shell). It's your friend. :)
[@z, @s, @i, @a].reject(&:empty?).inject { |m, e| m & e }
As you can read here the method Array#& returns a NEW Array and does not modify the original one, so you would have to change your code to something like:
Assuming you want the IDs that are contained in all the four arrays that are not empty, I would try something like this:
ary = []
ids = [@zip_ids, @sex_ids, @interest_ids, @age_ids]
ids.each {|i|ary << i unless i.empty?}
ary.uniq!
result = ids.inject(ary){|res, ids| res & ids}
As Glenn suggests, the most idiomatic way to do this is probably
users = [@zip_ids, @sex_ids, @interest_ids, @age_ids].reject(&:empty?).reduce(&:&)
However, this depends on the behavior of #hash and #eql? for the objects in the array. Since the implementation of Object#hash is based on object_id, Array#& for User objects (presumably using the default Object#hash impl) will compare object ids, not whatever values define equality semantics for your object.
A solution that allows you to compare arrays of User objects rather than just their ids would be to define #hash and #eql? semantics for your object and then use Array#&. For instance:
class User
# ...
def hash
[self.class, *equality_attributes].map(&:hash).reduce(:^)
end
def eql?(other)
self.class == other.class &&
self.equality_attributes == other.equality_attributes
end
# ...
private
def equality_attributes
[name, address]
end
end
精彩评论