Cara menentukan apakah satu array berisi semua elemen array lain

179

Diberikan:

a1 = [5, 1, 6, 14, 2, 8]

Saya ingin menentukan apakah mengandung semua elemen:

a2 = [2, 6, 15]

Dalam hal ini hasilnya adalah false.

Apakah ada metode Ruby / Rails bawaan untuk mengidentifikasi penyertaan array seperti itu?

Salah satu cara untuk mengimplementasikan ini adalah:

a2.index{ |x| !a1.include?(x) }.nil?

Apakah ada cara yang lebih baik dan lebih mudah dibaca?

Misha Moroshko
sumber
Jawaban yang diterima (pengurangan array) adalah solusi tercepat. Saya membandingkan semuanya di sini: gist.github.com/bbugh/cbbde8b48cbb16286044f6893e1f2e5f
brainbag

Jawaban:

309
a = [5, 1, 6, 14, 2, 8]
b = [2, 6, 15]

a - b
=> [5, 1, 14, 8]

b - a
=> [15]

(b - a).empty?
=> false
Geo
sumber
61
Ini cara untuk pergi. Mungkin sedikit disingkat menjadi(a2-a1).empty?
Holger Just
9
Ini hanya berfungsi untuk array yang merupakan set, bukan untuk array dengan duplikat
Chris
3
@ Chris - Anda bisa mencoba menggunakan Array # uniq untuk itu. Dengan contoh Holger Just, itu akan menjadi(a2.uniq - a1.uniq).empty?
Nick
stackoverflow.com/questions/13553822/… adalah yang saya maksud. Array # unique akan secara eksplisit gagal.
Chris
81

Mungkin ini lebih mudah dibaca:

a2.all? { |e| a1.include?(e) }

Anda juga dapat menggunakan persimpangan array:

(a1 & a2).size == a1.size

Catatan yang sizedigunakan di sini hanya untuk kecepatan, Anda juga dapat melakukan (lebih lambat):

(a1 & a2) == a1

Tapi saya kira yang pertama lebih mudah dibaca. Ini 3 adalah ruby ​​polos (bukan rel).

Pablo Fernandez
sumber
Jika menggunakan definisi OP a1 dan a2, dan a1 "berisi semua elemen" a2, saya pikir ini harus _ (a1 & a2) .size == a2.size _ karena a2 adalah array yang lebih kecil, yang seharusnya memiliki semua elemen yang termasuk dalam array yang lebih besar (untuk mendapatkan 'true') - maka persimpangan dari dua array harus sama panjangnya dengan array yang lebih kecil jika semua elemen di dalamnya hadir dalam array yang lebih besar.
JosephK
57

Ini bisa dicapai dengan melakukan

(a2 & a1) == a2

Ini menciptakan persimpangan kedua array, mengembalikan semua elemen a2yang juga ada di dalamnya a1. Jika hasilnya sama dengan a2, Anda dapat yakin bahwa Anda memiliki semua elemen yang disertakan a1.

Pendekatan ini hanya bekerja jika semua elemen di a2berbeda satu sama lain di tempat pertama. Jika ada ganda, pendekatan ini gagal. Yang dari Tempos masih berfungsi, jadi saya dengan sepenuh hati merekomendasikan pendekatannya (juga mungkin lebih cepat).

Holger Just
sumber
2
menggunakan lengthmetode ini akan jauh lebih baik
Pablo Fernandez
3
Ini tidak akan berfungsi jika set persimpangan memiliki elemen yang sama dalam urutan yang berbeda. Saya menemukan ini dengan cara yang sulit ketika mencoba menjawab pertanyaan ini: stackoverflow.com/questions/12062970/… kemudian menyadari banyak orang pintar telah melakukannya di sini!
CubaLibre
1
@CubaLibre Menarik. Apakah Anda memiliki beberapa data pengujian untuk mereproduksi ini? Dari pengujian saya sepertinya array yang dihasilkan mempertahankan urutan elemen dari array pertama (maka edit terbaru saya untuk jawaban saya). Namun, jika ini tidak benar, saya ingin belajar.
Holger Just
@ HolgerJust saya telah melakukan kesalahan dengan melakukan (a1 & a2) bukan (a2 & a1), itulah sebabnya saya melihat kesalahan. Anda benar tentang & mempertahankan pesanan dari array pertama.
CubaLibre
10

Jika tidak ada elemen duplikat atau Anda tidak peduli tentang mereka, maka Anda bisa menggunakan kelas Set :

a1 = Set.new [5, 1, 6, 14, 2, 8]
a2 = Set.new [2, 6, 15]
a1.subset?(a2)
=> false

Di balik layar ini menggunakan

all? { |o| set.include?(o) }
Kebingungan
sumber
1

Anda dapat menambal kelas Array:

class Array
    def contains_all?(ary)
        ary.uniq.all? { |x| count(x) >= ary.count(x) }
    end
end

uji

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
irb(main):137:0> %w[a b c d].contains_all? %w[d c h]
=> false
irb(main):138:0> %w[a b c d].contains_all? %w[d b c]
=> true

Tentu saja metode ini dapat ditulis sebagai metode standar saja, mis

def contains_all?(a,b)
    b.uniq.all? { |x| a.count(x) >= b.count(x) }
end

dan Anda bisa memintanya seperti

contains_all?(%w[a b c c], %w[c c c])

Memang, setelah profil, versi berikut ini jauh lebih cepat, dan kodenya lebih pendek.

def contains_all?(a,b)
    b.all? { |x| a.count(x) >= b.count(x) }
end
Zack Xu
sumber
0

Bergantung pada seberapa besar array Anda, Anda mungkin mempertimbangkan algoritma yang efisien O (n log n)

def equal_a(a1, a2)
  a1sorted = a1.sort
  a2sorted = a2.sort
  return false if a1.length != a2.length
  0.upto(a1.length - 1) do 
    |i| return false if a1sorted[i] != a2sorted[i]
  end
end

Mengurutkan biaya O (n log n) dan memeriksa setiap pasangan biaya O (n) sehingga algoritma ini adalah O (n log n). Algoritme lain tidak dapat lebih cepat (asimtotik) menggunakan array yang tidak disortir.

ayckoster
sumber
Anda dapat melakukannya di O (n) dengan semacam penghitungan.
klochner
Tidak Anda tidak bisa. Jenis penghitungan menggunakan alam semesta terbatas dan Ruby tidak memiliki batasan tentang cara mendapatkan angka besar.
ayckoster
Anda bisa, karena Anda tidak benar-benar harus mengurutkan item - Anda hanya perlu item pemetaan hash -> menghitung untuk kedua array, kemudian beralih di atas kunci dan membandingkan jumlah.
klochner
Apakah Anda yakin bahwa susunan # array menggunakan jenis gabungan?
Nate Symer
0

Sebagian besar jawaban berdasarkan (a1 - a2) atau (a1 & a2) tidak akan berfungsi jika ada elemen duplikat dalam array. Saya tiba di sini mencari cara untuk melihat apakah semua huruf dari kata (dibagi menjadi array) adalah bagian dari serangkaian huruf (misalnya untuk scrabble). Tidak satu pun dari jawaban ini yang berfungsi, tetapi jawaban ini berfungsi:

def contains_all?(a1, a2)
  try = a1.chars.all? do |letter|
    a1.count(letter) <= a2.count(letter)
  end
  return try
end
Charles Breton
sumber