Verifique se duas matrizes têm o mesmo conteúdo (em qualquer ordem)

90

Estou usando Ruby 1.8.6 com Rails 1.2.3 e preciso determinar se dois arrays têm os mesmos elementos, independentemente de estarem ou não na mesma ordem. Um dos arrays tem garantia de não conter duplicatas (o outro pode, caso em que a resposta é não).

Meu primeiro pensamento foi

require 'set'
a.to_set == b.to_set

mas eu queria saber se havia uma maneira mais eficiente ou idiomática de fazer isso.

Taymon
fonte
Tente array.should = ~ another_array check stackoverflow.com/questions/2978922/…
Athena
Você poderia ter evitado muita confusão: 1) declarando se os elementos dos arrays são necessariamente classificáveis; e 2) fornecer um exemplo simples para esclarecer o que você quer dizer com "se duas matrizes têm os mesmos elementos" (por exemplo, têm [1,2]e [2,1,1]têm os mesmos elementos?)
Cary Swoveland
Ruby 2.6 introduziu differenceque oferece uma solução muito rápida e muito legível. Mais informações aqui.
SRack

Respostas:

140

Isso não requer conversão para definir:

a.sort == b.sort
Mori
fonte
Sem conversão? O que é .uniq.sortentão? Além uniqé semelhante a to_setinternamente mais adicional.to_a.sort
Victor Moroz
Aceito por ser o mais próximo do que acabei usando, embora sem o uniqs. Na verdade acabei criando um dos arrays com Range#to_a, então só tive que fazer sorto outro.
Taymon
11
Isso não funcionará se a matriz contiver elementos que não podem ser simplesmente classificados (por exemplo, uma matriz de hashes). A solução de sahil dhankhar parece ser uma solução mais geral.
brad
40

para duas matrizes A e B: A e B têm o mesmo conteúdo se: (A-B).blank? and (B-A).blank?

ou você pode apenas verificar: ((A-B) + (B-A)).blank?

Também como sugerido por @ cort3z, esta solução também funciona para matrizes polimórficas, ou seja

 A = [1 , "string", [1,2,3]]
 B = [[1,2,3] , "string", 1]
 (A-B).blank? and (B-A).blank? => true
 # while A.uniq.sort == B.uniq.sort will throw error `ArgumentError: comparison of Fixnum with String failed` 

::::::::::: EDITAR ::::::::::::::

Como sugerido nos comentários, a solução acima falha para duplicatas. Embora de acordo com a pergunta que nem mesmo seja necessária, uma vez que o solicitante não está interessado em duplicatas (ele está convertendo suas matrizes para definir antes de verificar e isso mascara as duplicatas e mesmo se você olhar para a resposta aceita, ele está usando um operador .uniq antes de verificar e isso também mascara as duplicatas). Mesmo assim, se você tiver interesse em duplicatas, basta adicionar uma verificação de contagem para corrigir o mesmo (de acordo com a pergunta, apenas um array pode conter duplicatas). Portanto, a solução final será: A.size == B.size and ((A-B) + (B-A)).blank?

Sahil Dhankhar
fonte
Isso falhará se qualquer uma das matrizes contiver duplicatas. Por exemplo, se A=[1]e B=[1,1], ambos (A-B)e (B-A)retornarão em branco. Consulte a documentação do Array .
jtpereyda
@dafrazzman concordo totalmente com você. Modifiquei minha resposta para incorporar seu feedback. Mas se você olhar atentamente a pergunta (ou a resposta aceita), o autor da pergunta está usando: a.to_set == b.to_set e a resposta aceita está usando a.uniq.sort == b.uniq.sorte ambos fornecem exatamente o mesmo resultado que ((A-B) + (B-A)).blank?A = [1] e B = [1,1] concorda? Como ele estava apenas pedindo uma melhoria em relação à solução original, minha solução original ainda funciona :). aceita?
Sahil Dhankhar
1
Essa solução é muito boa, pois trata objetos de vários tipos. Digamos que você tenha A = [123, "test", [], some_object, nil]e B = A#because I am lazy, em seguida, A.uniq.sortirá lançar um erro (a comparação de string e Array falhou).
Automatico
Isso seria O (n), uma vez que depende do tamanho do array? (linear)
user3007294
1
Não funcionaria se os arrays tivessem o mesmo tamanho, mas os elementos repetidos não fossem os mesmos. Por exemplo A = [1, 1, 2]eB = [1, 2, 2]
Boudi
23

Comparsões de velocidade

require 'benchmark/ips'
require 'set'

a = [1, 2, 3, 4, 5, 6]
b = [1, 2, 3, 4, 5, 6]

Benchmark.ips do |x|
  x.report('sort')   { a.sort == b.sort }  
  x.report('sort!')  { a.sort! == b.sort! }  
  x.report('to_set') { a.to_set == b.to_set }  
  x.report('minus')  { ((a - b) + (b - a)).empty? }  
end  

Warming up --------------------------------------
            sort    88.338k i/100ms
           sort!   118.207k i/100ms
          to_set    19.339k i/100ms
           minus    67.971k i/100ms
Calculating -------------------------------------
            sort      1.062M  0.9%) i/s -      5.389M in   5.075109s
           sort!      1.542M  1.2%) i/s -      7.802M in   5.061364s
          to_set    200.302k  2.1%) i/s -      1.006M in   5.022793s
           minus    783.106k  1.5%) i/s -      3.942M in   5.035311s
Morozov
fonte
btw a ordem dos elemetns não afeta sorta velocidade de
Morozov
Me surpreendeu ... Eu esperava que a comparação por conjunto superasse todos os outros devido à complexidade de tempo O (n) de pesquisa de conjuntos. Assim, qualquer classificação bem implementada exigiria O (n logn). Ao passo que a conversão para conjuntos e a pesquisa de valores em geral chegariam a tempo O (n).
Oleg Afanasyev
1
Eu esperaria to_setcomeçar a ter um desempenho superior com matrizes grandes o suficiente, onde O (n logn) começaria a importar mais do que o esforço necessário para converter a matriz em conjunto
Andrius Chamentauskas
1
Isso é útil, mas não é realmente uma resposta em si? Talvez seja melhor adicionar isso a uma solução existente?
SRack
18

Ruby 2.6+

Ruby foi introduzido differenceem 2.6.

Isso fornece uma solução muito rápida e legível aqui, da seguinte maneira:

a = [1, 2, 3, 4, 5, 6]
b = [1, 2, 3, 4, 5, 6]

a.difference(b).any?
# => false
a.difference(b.reverse).any?
# => false

a = [1, 2, 3, 4, 5, 6]
b = [1, 2, 3]
a.difference(b).any?
# => true

Executando os benchmarks:

a = Array.new(1000) { rand(100) }
b = Array.new(1000) { rand(100) }

Benchmark.ips do |x|
  x.report('sort')   { a.sort == b.sort }  
  x.report('sort!')  { a.sort! == b.sort! }  
  x.report('to_set') { a.to_set == b.to_set }  
  x.report('minus')  { ((a - b) + (b - a)).empty? }  
  x.report('difference') { a.difference(b).any? }
end

      sort     13.908k  2.6%) i/s -     69.513k in   5.001443s
     sort!     14.656k  3.0%) i/s -     73.736k in   5.035744s
    to_set     5.125k   2.9%) i/s -     26.023k in   5.082083s
     minus     16.398k  2.2%) i/s -     83.181k in   5.074938s
difference     27.839k  5.2%) i/s -    141.048k in   5.080706s

Espero que ajude alguém!

SRack
fonte
2
a diferença é quebrar, neste caso, a = [1, 2, 3] b = [1, 2, 3, 4, 5] a.diferença (b). qualquer? => falso
erro-404
16

Quando os elementos de ae bsão Comparable,

a.sort == b.sort

Correção da resposta de @mori com base no comentário de @steenslag

Jared Beck
fonte
1
Bom e razoável.
Erwin Rooijakkers
4
... quando ae bpode ser classificado.
Cary Swoveland
8

Se você espera [:a, :b] != [:a, :a, :b] to_setnão funciona. Em vez disso, você pode usar a frequência:

class Array
  def frequency
    p = Hash.new(0)
    each{ |v| p[v] += 1 }
    p
  end
end

[:a, :b].frequency == [:a, :a, :b].frequency #=> false
[:a, :b].frequency == [:b, :a].frequency #=> true
Victor Moroz
fonte
por que não apenas a.sort == b.sortse ele se preocupa com a frequência?
fl00r
4
@ fl00r E se os itens não forem comparáveis? ["", :b].frequency == [:b, ""].frequency #=> true
Victor Moroz
2
você também pode fazer algo funcional comoa.group_by{|i| i} == b.group_by{|i| i}
fl00r
7

Se você souber que as matrizes têm o mesmo comprimento e nenhuma delas contém duplicatas, isso também funciona:

( array1 & array2 ) == array1

Explicação: o &operador, neste caso, retorna uma cópia de a1 sem quaisquer itens não encontrados em a2, que é o mesmo que o a1 original se ambas as matrizes tiverem o mesmo conteúdo sem duplicatas.

Análise: considerando que a ordem não foi alterada, estou supondo que isso seja implementado como uma iteração dupla de forma consistente O(n*n), notavelmente pior para grandes arrays do a1.sort == a2.sortque aquela que deveria funcionar com o pior caso O(n*logn).

Nat
fonte
2
Não funciona sempre: a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2retorna [2,1,3]para mim que não é igual aa1
Kalyan Raghu
@Kaylan, você não quer dizer que só funciona quando a1==a2? Pode funcionar se array1do lado direito da igualdade for substituído por array2, mas duvido que a ordem dos elementos retornados por &seja garantida.
Cary Swoveland,
1
@KalyanRaghu &é um operador de interseção de conjuntos para matrizes, &&é lógico AND - eles são muito diferentes!
Kimball,
2

Uma abordagem é iterar sobre a matriz sem duplicatas

# assume array a has no duplicates and you want to compare to b
!a.map { |n| b.include?(n) }.include?(false)

Isso retorna uma série de verdades. Se algum falso aparecer, o externo include?retornará verdadeiro. Portanto, você tem que inverter tudo para determinar se é uma correspondência.

Ron
fonte
@Victor Moroz, você está correto, e uma contagem de frequência seria simplesmente O (n).
Ron
2

combinando &e sizepode ser rápido também.

require 'benchmark/ips'
require 'set'

Benchmark.ips do |x|
  x.report('sort')   { a.sort == b.sort }  
  x.report('sort!')  { a.sort! == b.sort! }  
  x.report('to_set') { a.to_set == b.to_set }  
  x.report('minus')  { ((a - b) + (b - a)).empty? }
  x.report('&.size') { a.size == b.size && (a & b).size == a.size }  
end  

Calculating -------------------------------------
                sort    896.094k 11.4%) i/s -      4.458M in   5.056163s
               sort!      1.237M  4.5%) i/s -      6.261M in   5.071796s
              to_set    224.564k  6.3%) i/s -      1.132M in   5.064753s
               minus      2.230M  7.0%) i/s -     11.171M in   5.038655s
              &.size      2.829M  5.4%) i/s -     14.125M in   5.010414s
Todoroki
fonte