2012-06-15 3 views
1

Я хотел бы (быстро) определить, содержит ли один массив все элементы другого массива, принимая во внимание, что массивы могут иметь повторяющиеся элементы.Пересечения массивов с повторяющимися элементами в Ruby

Таким образом, я пытался что-то вроде этого:

alice = %w(a a a b) 
bob = %w(a a b c d e) 
alice & bob => ["a", "b"] 
alice - bob => [] 

Но то, что я хотел бы это оператор, который позволит мне определить, что боб не включает в себя все элементы Алисе, потому что боб не хватает «a» персонажей.

+2

[? Что вы пробовали] (http://whathaveyoutried.com/) – sczizzo

+0

Все основные методы не учитывают дубликаты, поэтому вам, вероятно, придется написать свой собственный п. –

+0

Я новичок в Ruby, а ядро ​​/ std-библиотека довольно обширна, поэтому я хотел посмотреть, есть ли у кого-то простой ответ, прежде чем я напишу что-нибудь ненужное. – Dave

ответ

3
alice.select{|x| alice.count(x) > bob.count(x)} 

Update Настройка тест:

require 'benchmark' 

def subset_multivalue?(a, b) 
    bb = b.clone 
    a.each do |e| 
    i = bb.index(e) 
    if i 
     bb.delete_at(i) 
    else 
     return false 
    end 
    end 
    return true 
end 

def subset_multivalue2?(a, b) 
    a.find{|x| a.count(x) > b.count(x)} 
end 

def subset_multivalue3?(alice, bob) 
    alice_counts = alice.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 } 
    bob_counts = bob.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 } 

    alice_counts.all? do |k, v| 
    bob_counts.has_key?(k) && bob_counts[k] >= v 
    end 
end 

alice = %w(a a a b) 
bob = %w(a a b c d e) 

Benchmark.bm do |x| 
    x.report("dave:") do 
    1000000.times do 
     subset_multivalue?(alice, bob) 
    end 
    end 

    x.report("me:") do 
    1000000.times do 
     subset_multivalue2?(alice,bob) 
    end 
    end 

    x.report("andrew:") do 
    1000000.times do 
     subset_multivalue3?(alice,bob) 
    end 
    end 
end 

Результаты:

 user  system  total  real 
dave: 15.054000 0.000000 15.054000 (15.108864) 
me: 11.529000 0.031000 11.560000 (11.689669) 
andrew: 65.036000 0.047000 65.083000 (67.463859) 
+0

Вау, так просто. Но это было более чем на 2 раза медленнее, чем моя версия; Я попробовал пару вариантов, похоже, что 'count (Array)' просто велик. – Dave

+0

Ну, ваши останавливаются после первого появления. Если это то, что вы хотите изменить, выберите «выбрать», чтобы «найти». – pguardiario

+0

Это первая вариация, которую я пробовал. Это помогает, но 'count (Array)' все еще является виновником. – Dave

3

Вероятно, проще всего подсчитать, как часто каждый элемент происходит таким образом, мы не должны слишком беспокоиться о отслеживанием дублей мы/не посчитали:

alice_counts = alice.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 } 
#=> {"a"=>3, "b"=>1} 

bob_counts = bob.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 } 
#=> {"a"=>2, "b"=>1, "c"=>1, "d"=>1, "e"=>1} 

Затем убедитесь, что каждый ключ в alice_counts имеет значение, равное или большее в bob_counts:

alice_counts.all? { |k, v| bob_counts[k] >= v } 
#=> false 
+1

Как человек, изучающий Ruby и обладающий ограниченным опытом работы с функциональными языками, я постоянно поражаюсь силе языка в таком сжатом объеме кода. (+1) – JasCav

+0

Это решение проверяет точное соответствие; вроде того же, что и оператор ==. Я расширил класс массива с помощью этого решения (он называется 'aaa'). 'alice.aaa bob # => false' как ожидалось, и' bob.aaa bob # => true', как и ожидалось. Тем не менее, 'bob.dup.send (: +, [bob.last]). Aaa bob # => false', когда новый массив все еще имеет все элементы' bob'. Я дублировал bob, чтобы подчеркнуть, что они разные объекты. Может быть, я неправильно прочитал вопрос, и это необходимая функциональность, но я хотел бы указать на это. – iGbanam

+0

Ужасно, если вы меняете объекты в сравнении, тест проходит. Итак, 'bob.aaa bob.dup.send: +, [bob.last] # => true'. Интересно – iGbanam

0

Я думаю, я остановился на этом:

def subset_multivalue?(a, b) 
    bb = b.clone 
    a.each do |e| 
      i = bb.index(e) 
      if i 
        bb.delete_at(i) 
      else 
        return false 
      end 
    end 
    return true 
end 

Я понимаю, что он не очень рубиновый, но он, похоже, выполняет эту работу.

0

Перечислимый # group_by() будет выбором.

alice = %w(a a a b) 
bob = %w(a a b c d e) 
alice_group = alice.group_by{|a| a}.map{|k,v| [k ,v.length]} 
#=> [["a", 3], ["b", 1]] 
bob_group = bob.group_by{|a| a}.map{|k,v| [k ,v.length]} 
#=> [["a", 2], ["b", 1], ["c", 1], ["d", 1], ["e", 1]] 
alice_group-bob_group 
#=> [["a", 3]] 
bob_group-alice_group 
#=>[["a", 2], ["c", 1], ["d", 1], ["e", 1]] 
Смежные вопросы