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


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.