Як визначити, чи містить один масив усі елементи іншого масиву


179

Подано:

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

Я хотів би визначити, чи він містить усі елементи:

a2 = [2, 6, 15]

У цьому випадку результат є false.

Чи є якісь вбудовані методи Ruby / Rails для ідентифікації такого включення масиву?

Одним із способів здійснення цього є:

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

Чи є кращий, читабельніший спосіб?


Прийнята відповідь (віднімання масиву) - це найшвидше рішення. Я тут ознайомив
мішок,

Відповіді:


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

61
Це шлях. Це може бути трохи скорочене до(a2-a1).empty?
Holger Тільки

9
Це працює лише для масивів, які є наборами, а не для масивів з дублікатами
Кріс,

3
@Chris - Ви можете спробувати використовувати Array # uniq для цього. З прикладом Холгера Юста, це було б(a2.uniq - a1.uniq).empty?
Нік

stackoverflow.com/questions/13553822/… - це те, що я мав на увазі. Масив # унікальний явно не зможе цього.
Кріс

81

Можливо, це простіше читати:

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

Ви також можете використовувати перетин масиву:

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

Зауважте, що sizeтут використовується лише для швидкості, ви також можете робити (повільніше):

(a1 & a2) == a1

Але я здогадуюсь, перше читабельніше. Ці 3 - звичайний рубін (не рейки).


Якщо ви використовуєте визначення OP для a1 та a2 та a1 ", що містять усі елементи" a2, я думаю, що це має бути _ (a1 & a2) .size == a2.size _, оскільки a2 - менший масив, у якому повинні бути всі елементи, що входять до більшого масиву (щоб отримати 'true') - отже, перетин двох масивів повинен бути такої ж довжини, як масив меншої маси, якщо всі елементи в ньому присутні у більшому.
ДжозефК

57

Цього можна досягти, роблячи

(a2 & a1) == a2

Це створює перетин обох масивів, повертаючи всі елементи, з a2яких також є a1. Якщо результат такий, як a2, ви можете бути впевнені, що всі елементи включені в a1.

Цей підхід працює лише в тому випадку, якщо всі елементи в a2першу чергу відрізняються один від одного. Якщо є парні, такий підхід не вдається. Той із Tempos все ще працює, тому я від усієї душі рекомендую його підхід (також, мабуть, швидше).


2
використовуючи lengthметод, вийде набагато краще
Пабло Фернандес

3
Це не спрацює, якщо набір перехрестя має ті самі елементи в іншому порядку. Я зрозумів це на важкому шляху, намагаючись відповісти на це питання: stackoverflow.com/questions/12062970/… пізніше зрозумів, що багато розумних людей вже зробили це тут!
CubaLibre

1
@CubaLibre Цікаво. Чи є у вас якісь тестові дані для відтворення цього? Для моїх тестів здавалося, ніби отриманий масив зберігає порядок елементів з першого масиву (звідси моя остання редакція моєї відповіді). Однак якщо це дійсно не так, я хотів би дізнатися.
Холгер Тільки

@HolgerJust я допустив помилку (a1 & a2) замість (a2 & a1), тому я бачив помилку. Ви маєте рацію щодо збереження замовлення з першого масиву.
CubaLibre

10

Якщо немає дублікатів елементів або ви не переймаєтесь ними, ви можете використовувати клас Set :

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

За лаштунками це використовується

all? { |o| set.include?(o) }

1

Ви можете промацувати клас Array:

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

тест

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

Звичайно, метод може бути записаний як стандартний метод, наприклад

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

і ви можете посилатися на це так

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

Дійсно, після профілювання наступна версія набагато швидша, а код коротший.

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

0

Залежно від того, наскільки великі ваші масиви, ви можете розглянути ефективний алгоритм 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

Сортування витрат O (n log n) та перевірка кожної пари коштує O (n), таким чином цей алгоритм є O (n log n). Інші алгоритми не можуть бути швидшими (асимптотично) за допомогою несортованих масивів.


Ви можете зробити це в O (n) за допомогою підрахунку.
klochner

Ні, ви не можете. Для підрахунку сортування використовується обмежений Всесвіт, і у Рубі немає обмежень щодо того, як можна отримати великі числа.
ayckoster

Ви можете, тому що вам фактично не потрібно сортувати елементи - вам просто потрібен елемент хеш-зіставлення -> рахувати для обох масивів, потім перебирати ключі та порівнювати підрахунки.
klochner

Ви впевнені, що Array # sort використовує сортування злиття?
Нейт Симер

0

Більшість відповідей на основі (a1 - a2) або (a1 & a2) не працюватимуть, якщо в будь-якому масиві є дублюючі елементи. Я приїхав сюди, шукаючи спосіб переконатися, чи всі букви слова (розділені на масив) входять до набору букв (наприклад, для скребблювання). Жодна з цих відповідей не спрацювала, але ця:

def contains_all?(a1, a2)
  try = a1.chars.all? do |letter|
    a1.count(letter) <= a2.count(letter)
  end
  return try
end
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.