I need to tell if an array contains all of the elements of another array with duplicates.
[1,2,3].contains_all? [1,2] #=> true
[1,2,3].contains_all
class Array
def contains_all? other
other = other.dup
each{|e| if i = other.index(e) then other.delete_at(i) end}
other.empty?
end
end
Counting the number of occurrences and comparing them seems to be the obvious way to go.
class Array
def contains_all? arr
h = self.inject(Hash.new(0)) {|h, i| h[i] += 1; h}
arr.each do |i|
return false unless h.has_key?(i)
return false if h[i] == 0
h[i] -= 1
end
true
end
end
Answering with my own implementation, but definitely want to see if someone can come up with a more efficient way. (I won't accept my own answer)
class Array
def contains_all?(a2)
a2.inject(self.dup) do |copy, el|
if copy.include? el
index = copy.index el
copy.delete_at index
else
return false
end
copy
end
true
end
end
And the tests:
1.9.3p194 :016 > [1,2,3].contains_all? [1,2] #=> true
=> true
1.9.3p194 :017 > [1,2,3].contains_all? [1,2,2] #=> false (this is where (a1-a2).empty? fails)
=> false
1.9.3p194 :018 > [2,1,2,3].contains_all? [1,2,2] #=> true
=> true
class Array
def contains_all?(ary)
ary.uniq.all? { |x| count(x) >= ary.count(x) }
end
end
test
irb(main):131:0> %w[a b c c].contains_all? %w[a b c]
=> true
irb(main):132:0> %w[a b c c].contains_all? %w[a b c c]
=> true
irb(main):133:0> %w[a b c c].contains_all? %w[a b c c c]
=> false
irb(main):134:0> %w[a b c c].contains_all? %w[a]
=> true
irb(main):135:0> %w[a b c c].contains_all? %w[x]
=> false
irb(main):136:0> %w[a b c c].contains_all? %w[]
=> true
The following version is faster and shorter in code.
class Array
def contains_all?(ary)
ary.all? { |x| count(x) >= ary.count(x) }
end
end
If you can't find a method, you can build one using ruby's include? method.
Official documentation: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-include-3F
Usage:
array = [1, 2, 3, 4]
array.include? 3 #=> true
Then, you can do a loop:
def array_includes_all?( array, comparision_array )
contains = true
for i in comparision_array do
unless array.include? i
contains = false
end
end
return contains
end
array_includes_all?( [1,2,3,2], [1,2,2] ) #=> true
This solution will only iterate through both lists once, and hence run in linear time. It might however be too much overhead if the lists are expected to be very small.
class Array
def contains_all?(other)
return false if other.size > size
elem_counts = other.each_with_object(Hash.new(0)) { |elem,hash| hash[elem] += 1 }
each do |elem|
elem_counts.delete(elem) if (elem_counts[elem] -= 1) <= 0
return true if elem_counts.empty?
end
false
end
end