Проверить, имеют ли два массива одинаковое содержимое (в любом порядке)

91

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

Моя первая мысль была

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

но мне было интересно, есть ли более эффективный или идиоматический способ сделать это.

Таймон
источник
Попробуйте array.should = ~ another_array проверить stackoverflow.com/questions/2978922/…
Афина
Вы могли избежать путаницы, если: 1) указали, можно ли сортировать элементы массивов; и 2) приведу простой пример, чтобы прояснить, что вы имеете в виду под «имеют ли два массива одинаковые элементы» (например, имеют ли [1,2]и [2,1,1]имеют ли одни и те же элементы?)
Кэри Свовленд
Ruby 2.6 представил differenceрешение, которое предлагает очень быстрое и удобочитаемое решение. Больше информации здесь.
SRack

Ответы:

144

Это не требует преобразования для установки:

a.sort == b.sort
Мори
источник
Нет конверсии? Что .uniq.sortтогда? К тому uniqже похоже на to_setвнутренне плюс дополнительный.to_a.sort
Виктор Мороз
Принимая это, поскольку это ближе всего к тому, что я использовал, хотя и без uniqs. На самом деле я закончил тем, что создал один из массивов Range#to_a, поэтому мне оставалось только sortдругое.
Taymon 07
11
Это не сработает, если массив содержит элементы, которые нельзя просто отсортировать (например, массив хешей). Решение сахила дханкара кажется более общим решением.
Брэд
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 02
@dafrazzman полностью с вами согласен. Я изменил свой ответ, чтобы учесть ваш отзыв. Но если вы внимательно посмотрите на вопрос (или принятый ответ), спрашивающий использует:, a.to_set == b.to_set а принятый ответ использует, a.uniq.sort == b.uniq.sortи оба дают тот же результат, что и ((A-B) + (B-A)).blank?для A = [1] и B = [1,1] согласны? Поскольку он просто просил улучшить свое исходное решение, мое исходное решение все еще работает :). дать согласие?
Сахил Дханкхар
1
Это удобное решение, поскольку оно обрабатывает объекты нескольких типов. Скажем, у вас есть A = [123, "test", [], some_object, nil]и B = A#because I am lazy, тогда A.uniq.sortбудет выдана ошибка (сравнение строки и массива не выполнено).
Automatico,
Будет ли это O (n), поскольку это зависит от размера массива? (linear)
user3007294
1
Это не сработает, если массивы имеют одинаковый размер, но повторяющиеся элементы не одинаковы. Например A = [1, 1, 2]иB = [1, 2, 2]
Boudi
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) начнет иметь большее значение, чем усилия, необходимые для преобразования массива в набор,
Андрюс Чаментаускас
1
Это полезно, но само по себе не является ответом? Возможно, лучше добавить это к существующему решению?
SRack
21

Рубин 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

Надеюсь, это кому-то поможет!

SRack
источник
5
В этом случае разница нарушается a = [1, 2, 3] b = [1, 2, 3, 4, 5] a.difference (b) .any? => false
error-404
17

Когда элементы aи bесть Comparable,

a.sort == b.sort

Исправление ответа @ mori на основе комментария @ steenslag

Джаред Бек
источник
4
... когда aи bможно сортировать.
Кэри Свовленд,
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 06
4
@ fl00r Что делать, если товары не сопоставимы? ["", :b].frequency == [:b, ""].frequency #=> true
Виктор Мороз
2
также вы можете сделать что-нибудь функциональное какa.group_by{|i| i} == b.group_by{|i| i}
fl00r 06
7

Если вы знаете, что массивы имеют одинаковую длину и ни один из них не содержит дубликатов, это тоже работает:

( array1 & array2 ) == array1

Объяснение:& оператор в этом случае возвращает копию a1 SANs пункты , не найденную в а2, которая является такой же , как оригинал a1 тогда и только тогда оба массива имеют одинаковое содержимое без каких - либо дубликатов.

Анализ: Учитывая, что порядок не изменился, я предполагаю, что это реализовано как двойная итерация так последовательно 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
Kalyan Raghu
@ Кайлан, разве это не работает, только когда a1==a2? Это может сработать, если array1в правой части равенства заменить на array2, но я сомневаюсь, что порядок элементов, возвращаемых с помощью &, гарантирован.
Cary Swoveland
1
@KalyanRaghu &- это оператор пересечения множеств для массивов, &&логический И - они очень разные!
Kimball
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?вернет истину. Таким образом, вам нужно перевернуть все, чтобы определить, соответствует ли это совпадению.

Рон
источник
@ Виктор Мороз, вы правы, и счетчик частоты будет просто O (n).
Рон