Перевірте, чи мають два масиви однаковий вміст (у будь-якому порядку)


90

Я використовую Ruby 1.8.6 з Rails 1.2.3, і мені потрібно визначити, чи мають два масиви однакові елементи, незалежно від того, чи вони в одному порядку. Один із масивів гарантовано не міститиме дублікатів (інший може, у такому випадку відповідь негативна).

Моя перша думка була

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

але мені було цікаво, чи існує більш ефективний чи ідіоматичний спосіб зробити це.



Спробуйте array.should = ~ another_array перевірка stackoverflow.com/questions/2978922 / ...
Athena

Ви могли б врятувати багато плутанини, якщо: 1) вказали, чи елементи масивів обов’язково сортуються; і 2) навести простий приклад, щоб пояснити, що ви маєте на увазі під назвою "чи мають два масиви однакові елементи" (наприклад, мають [1,2]і [2,1,1]мають однакові елементи?)
Кері Свовеленд

Представлений Ruby 2.6, differenceякий пропонує рішення як дуже швидке, так і дуже читабельне. Більше інформації тут.
SRack

Відповіді:


140

Для цього не потрібно перетворення для встановлення:

a.sort == b.sort

Немає конверсії? Що .uniq.sortтоді? До того uniqж схоже на to_setвнутрішньо плюс додаткове.to_a.sort
Віктор Мороз

Приймаючи це, оскільки це найближче до того, що я в кінцевому підсумку використовував, хоча без uniqs. Насправді я в кінцевому підсумку створив один із масивів Range#to_a, тому мені довелося лише sortіншому.
Таймон

11
Це не спрацює, якщо масив містить елементи, які неможливо просто сортувати (наприклад, масив хешів). Рішення sahil dhankhar представляється більш загальним рішенням.
Бред

40

для двох масивів A і B: A і B мають однаковий вміст, якщо: (A-B).blank? and (B-A).blank?

або ви можете просто перевірити: ((A-B) + (B-A)).blank?

Також, як пропонує @ cort3z, це рішення als0 працює для поліморфних масивів, тобто

 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` 

::::::::::: РЕДАКТУВАТИ ::::::::::::::

Як пропонується в коментарях, наведене вище рішення не вдається виконати дублікати. Хоча, згідно з питанням, яке навіть не потрібне, оскільки запитувач не зацікавлений у дублікатах (він перетворює свої масиви на встановлення перед перевіркою і маскує дублікати, навіть якщо ви подивитесь на прийнята відповідь, яку він використовує оператор .uniq перед перевіркою, і це теж маскує дублікати.). Але все ж, якщо дублікати вас цікавлять, просто додавши перевірку підрахунку, це буде виправлено (відповідно до питання, лише один масив може містити дублікати). Отже, остаточним рішенням буде: A.size == B.size and ((A-B) + (B-A)).blank?


Це не вдасться, якщо будь-який з масивів містить дублікати. Наприклад, якщо A=[1]і B=[1,1], і те, (A-B)і інше (B-A)повернеться порожнім. Див. Документацію до масиву .
jtpereyda

@dafrazzman повністю з вами згоден. Я змінив свою відповідь, щоб врахувати ваші відгуки. Але якщо ви уважно подивитесь на питання (або прийняту відповідь), asker використовує: a.to_set == b.to_set і прийнята відповідь використовує, a.uniq.sort == b.uniq.sortі обидва дають точно такий же результат, як ((A-B) + (B-A)).blank?для A = [1] і B = [1,1] згодні? Оскільки він просто просив покращити своє оригінальне рішення, моє оригінальне рішення все ще працює :). погодитися?
Sahil Dhankhar

1
Це рішення є досить приємним, оскільки воно обробляє об'єкти різних типів. Скажімо, що у вас є, A = [123, "test", [], some_object, nil]а B = A#because I am lazyпотім A.uniq.sortвикине помилку (не вдалося порівняти рядок та масив).
Автоматично,

Тоді це буде O (n), оскільки це залежить від розміру масиву? (лінійний)
user3007294

1
Це не спрацювало б, якщо масиви мають однаковий розмір, але повторювані елементи не однакові. Наприклад A = [1, 1, 2]іB = [1, 2, 2]
Боуді

23

Порівняння швидкості

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

До речі порядок елементів не впливає sortна швидкість руху
Морозов,

Мене здивувало ... Я очікував , що порівняння наборів перевершить усі інші завдяки складності часу пошуку O (n). Так що для будь-якого добре реалізованого сортування знадобиться O (n logn). Тоді як відливання на множини та пошук значень загалом зробить це за O (n) час.
Олег Афанасьєв

1
Я очікував to_setби почати перевершувати роботу з досить великими масивами, де O (n logn) почне мати значення більше, ніж зусилля, необхідні для перетворення масиву на встановлення
Andrius Chamentauskas

1
Це корисно, але насправді не відповідь саме по собі? Можливо, краще додати це до існуючого рішення?
SRack

19

Рубін 2.6+

Ruby представлений differenceв 2.6.

Це дає дуже швидке, дуже читабельне рішення тут:

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

Запуск тестів:

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

Сподіваюся, це комусь допомагає!


4
різниця порушується в цьому випадку a = [1, 2, 3] b = [1, 2, 3, 4, 5] a.різниця (b) .будь-яка? => false
помилка-404


8

Якщо ви очікуєте [:a, :b] != [:a, :a, :b] to_set, не працює. Замість цього ви можете використовувати частоту:

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

чому б не просто, a.sort == b.sortякщо він дбає про частоту?
fl00r

4
@ fl00r Що робити, якщо предмети не можна порівняти? ["", :b].frequency == [:b, ""].frequency #=> true
Віктор Мороз

2
також ви можете зробити щось функціональне, якa.group_by{|i| i} == b.group_by{|i| i}
fl00r

7

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

( array1 & array2 ) == array1

пояснення:& оператор в цьому випадку повертає копію a1 SANs пункти , не знайдений в А2, яка є такою ж , як оригінал a1 тоді і тільки тоді обидва масиви мають однаковий вміст без яких - або дублікатів.

Analyis: Враховуючи те, що порядок залишається незмінним, я здогадуюсь, що це реалізовано як подвійну ітерацію настільки послідовно O(n*n), особливо гірше для великих масивів, ніж a1.sort == a2.sortце має виконуватися в гіршому випадку O(n*logn).


2
Не працює завжди: a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2повернення [2,1,3]для мене, що не дорівнюєa1
Калян Рагу

@Kaylan, ти не маєш на увазі, що це працює лише тоді a1==a2? Це може спрацювати, якщо array1праву частину рівності замінити на array2, але я сумніваюся, що порядок повернених елементів &гарантований.
Кері Своуленд

1
@KalyanRaghu &- це набір операторів перетину для масивів, &&це логічно І - вони дуже різні!
Кімбол,

3

комбінування &і sizeможе бути також швидким.

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

2

Одним із підходів є ітерація масиву без дублікатів

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

Це повертає масив істин. Якщо з'явиться якесь помилкове значення, тоді зовнішнє include?поверне true. Таким чином, вам доведеться перевернути все, щоб визначити, чи це збіг.


@Victor Moroz, ти маєш рацію, і підрахунок частоти буде просто O (n).
Рон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.