Periksa apakah dua larik memiliki konten yang sama (dalam urutan apa pun)

90

Saya menggunakan Ruby 1.8.6 dengan Rails 1.2.3, dan perlu menentukan apakah dua array memiliki elemen yang sama, terlepas dari apakah urutannya sama atau tidak. Salah satu array dijamin tidak mengandung duplikat (yang lain mungkin, dalam hal ini jawabannya tidak).

Pikiran pertama saya adalah

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

tetapi saya bertanya-tanya apakah ada cara yang lebih efisien atau idiomatis untuk melakukannya.

Taymon
sumber
Coba array.should = ~ another_array periksa stackoverflow.com/questions/2978922/…
Athena
Anda bisa menghindari banyak kebingungan dengan: 1) menyatakan apakah elemen dari array harus diurutkan; dan 2) memberikan contoh sederhana untuk memperjelas apa yang Anda maksud dengan, "apakah dua array memiliki elemen yang sama" (misalnya, apakah [1,2]dan [2,1,1]memiliki elemen yang sama?)
Cary Swoveland
Ruby 2.6 telah diperkenalkan differenceyang menawarkan solusi yang sangat cepat dan sangat mudah dibaca. Info selengkapnya di sini.
SRack

Jawaban:

140

Ini tidak memerlukan konversi untuk ditetapkan:

a.sort == b.sort
Mori
sumber
Tidak ada konversi? Lalu apa .uniq.sort? Selain uniqitu mirip dengan to_setinternal plus tambahan.to_a.sort
Victor Moroz
Menerima ini karena paling dekat dengan apa yang akhirnya saya gunakan, meskipun tanpa uniqs. Sebenarnya saya akhirnya membuat salah satu array dengan Range#to_a, jadi saya hanya perlu sortyang lain.
Taymon
11
Ini tidak akan berfungsi jika larik berisi elemen yang tidak dapat disortir begitu saja (misalnya, larik hash). Solusi sahil dhankhar tampaknya menjadi solusi yang lebih umum.
brad
40

untuk dua larik A dan B: A dan B memiliki konten yang sama jika: (A-B).blank? and (B-A).blank?

atau Anda bisa memeriksa: ((A-B) + (B-A)).blank?

Juga seperti yang disarankan oleh @ cort3z, solusi ini juga bekerja untuk array polimorfik yaitu

 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` 

::::::::::: EDIT :::::::::::::

Seperti yang disarankan dalam komentar, solusi di atas gagal untuk duplikat Meskipun sesuai pertanyaan yang bahkan tidak diperlukan karena penanya tidak tertarik pada duplikat (dia mengubah arraynya ke set sebelum memeriksa dan masker itu duplikat dan bahkan jika Anda melihatnya jawaban yang diterima dia menggunakan operator .uniq sebelum memeriksa dan itu juga menutupi duplikat.). Tetapi tetap saja jika duplikat menarik minat Anda, Menambahkan pemeriksaan hitungan akan memperbaikinya (sesuai pertanyaan hanya satu array yang dapat berisi duplikat). Jadi solusi akhirnya adalah: A.size == B.size and ((A-B) + (B-A)).blank?

Sahil Dhankhar
sumber
Ini akan gagal jika salah satu array berisi duplikat. Misalnya, jika A=[1]dan B=[1,1], keduanya (A-B)dan (B-A)akan kembali kosong. Lihat Dokumentasi Array .
jtpereyda
@dafrazzman setuju dengan Anda. Saya telah mengubah jawaban saya untuk memasukkan umpan balik Anda. Tetapi jika Anda melihat lebih dekat pada pertanyaan (atau jawaban yang diterima), penanya menggunakan: a.to_set == b.to_set dan jawaban yang diterima menggunakan a.uniq.sort == b.uniq.sortdan keduanya memberikan hasil yang sama persis seperti ((A-B) + (B-A)).blank?untuk A = [1] dan B = [1,1] setuju? Karena dia hanya meminta perbaikan atas solusi aslinya, solusi asli saya masih berfungsi :). setuju?
Sahil Dhankhar
1
Solusi ini cukup bagus karena menangani objek dari berbagai jenis. Katakanlah Anda memiliki A = [123, "test", [], some_object, nil]dan B = A#because I am lazy, kemudian A.uniq.sortakan membuang kesalahan (perbandingan string dan Array gagal).
Automatico
Apakah ini akan menjadi O (n) karena itu tergantung pada ukuran array? (linier)
pengguna3007294
1
Ini tidak akan berfungsi jika array memiliki ukuran yang sama tetapi elemen yang berulang tidak sama. Misalnya A = [1, 1, 2]danB = [1, 2, 2]
Boudi
23

Perbandingan kecepatan

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
sumber
Urutan btw elemetns tidak mempengaruhi sortkecepatan
Morozov
Mengejutkan saya ... Saya mengharapkan perbandingan by-set mengungguli yang lain karena kompleksitas waktu set lookup O (n). Sehingga setiap jenis yang diterapkan dengan baik akan membutuhkan O (n logn). Sedangkan casting ke set dan mencari nilai secara keseluruhan akan membuatnya dalam waktu O (n).
Oleg Afanasyev
1
Saya berharap to_setuntuk mulai mengungguli dengan array yang cukup besar di mana O (n logn) akan mulai lebih penting daripada upaya yang diperlukan untuk mengonversi array ke set
Andrius Chamentauskas
1
Ini membantu, tetapi sebenarnya bukan jawaban itu sendiri? Mungkin lebih baik menambahkan ini ke solusi yang sudah ada?
SRack
19

Ruby 2.6+

Ruby diperkenalkan differencedi 2.6.

Ini memberikan solusi yang sangat cepat dan mudah dibaca di sini, sebagai berikut:

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

Menjalankan benchmark:

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

Semoga itu bisa membantu seseorang!

SRack
sumber
4
selisih putus dalam hal ini a = [1, 2, 3] b = [1, 2, 3, 4, 5] a. selisih (b). apa? => false
error-404
16

Ketika elemen dari adan badalah Comparable,

a.sort == b.sort

Koreksi jawaban @ mori berdasarkan komentar @ steenslag

Jared Beck
sumber
1
Bagus dan masuk akal.
Erwin Rooijakkers
4
... kapan adan bdapat diurutkan.
Cary Swoveland
8

Jika Anda berharap [:a, :b] != [:a, :a, :b] to_settidak berhasil. Anda dapat menggunakan frekuensi sebagai gantinya:

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
sumber
mengapa tidak hanya a.sort == b.sortjika dia peduli dengan frekuensi?
fl00r
4
@ fl00r Bagaimana jika item tidak sebanding? ["", :b].frequency == [:b, ""].frequency #=> true
Victor Moroz
2
Anda juga dapat melakukan sesuatu yang fungsional sepertia.group_by{|i| i} == b.group_by{|i| i}
fl00r
7

Jika Anda mengetahui bahwa array memiliki panjang yang sama dan tidak ada array yang berisi duplikat maka ini juga berfungsi:

( array1 & array2 ) == array1

Penjelasan: yang &operator dalam hal ini mengembalikan salinan sans a1 setiap item tidak ditemukan dalam a2, yang sama dengan a1 asli IFF kedua array memiliki isi yang sama dengan tidak ada duplikat.

Analisis: Mengingat bahwa urutannya tidak berubah, saya menduga ini diterapkan sebagai iterasi ganda secara konsisten O(n*n), terutama lebih buruk untuk array besar daripada a1.sort == a2.sortyang seharusnya bekerja dengan kasus terburuk O(n*logn).

Nat
sumber
2
Tidak selalu berhasil: a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2pengembalian [2,1,3]untuk saya yang tidak sama dengana1
Kalyan Raghu
@Kaylan, bukankah maksudmu itu hanya berfungsi ketika a1==a2? Ini dapat berfungsi jika array1di sisi kanan persamaan diganti oleh array2, tetapi saya ragu bahwa urutan elemen yang dikembalikan oleh &dijamin.
Cary Swoveland
1
@KalyanRaghu &adalah operator persimpangan set untuk array, &&logis DAN - mereka sangat berbeda!
Kimball
3

menggabungkan &dan sizemungkin juga cepat.

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
sumber
2

Salah satu pendekatannya adalah mengulang array tanpa duplikat

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

Ini mengembalikan larik trues. Jika salah muncul, maka bagian luar include?akan mengembalikan nilai benar. Jadi, Anda harus membalik semuanya untuk menentukan apakah itu cocok.

Ron
sumber
@Victor Moroz, Anda benar, dan penghitungan frekuensi akan menjadi O (n).
Ron