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


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

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


5
В этом случае разница нарушается a = [1, 2, 3] b = [1, 2, 3, 4, 5] a.difference (b) .any? => false
— error-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 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).
— Рон
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.