Подтвердить что ты не робот

Ruby: Как найти элемент в массиве, который имеет наибольшее количество вхождений?

[1, 1, 1, 2, 3].mode
=> 1

['cat', 'dog', 'snake', 'dog'].mode
=> dog
4b9b3361

Ответ 1

Сначала создайте хеш, сопоставляя каждое значение в массиве с его частотой...

arr = [1, 1, 1, 2, 3]

freq = arr.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
#=> {1=>3, 2=>1, 3=>1}

... затем используйте таблицу частот, чтобы найти элемент с самой высокой частотой:

arr.max_by { |v| freq[v] }
#=> 1

Ответ 2

В то время как я обожаю решение grep за его элегантность и напоминаю (или учу) меня о методе в Enumerable, который я забыл (или пропустил полностью), он медленный, медленный, медленный. Я согласен на 100%, что создание метода Array#mode - хорошая идея, однако - это Ruby, нам не нужна библиотека функций, которая действует на массивы, мы можем создать mixin, который добавляет необходимые функции в класс Array сам по себе.

Но альтернатива inject (Hash) использует сортировку, которая нам также не нужна: нам просто нужно значение с наивысшим значением.

Ни одно из решений не учитывает возможность того, что более одного значения может быть режимом. Может быть, это не проблема в задаче, как указано (не могу сказать). Я думаю, мне хотелось бы знать, есть ли связь, и, во всяком случае, я думаю, что мы можем немного улучшить производительность.

require 'benchmark'

class Array
  def mode1
    sort_by {|i| grep(i).length }.last
  end
  def mode2
    freq = inject(Hash.new(0)) { |h,v| h[v] += 1; h }
    sort_by { |v| freq[v] }.last    
  end
  def mode3
    freq = inject(Hash.new(0)) { |h,v| h[v] += 1; h }
    max = freq.values.max                   # we're only interested in the key(s) with the highest frequency
    freq.select { |k, f| f == max }         # extract the keys that have the max frequency
  end
end

arr = Array.new(1_000) { |i| rand(100) }    # something to test with

Benchmark.bm(30) do |r|
  res = {}
  (1..3).each do |i|
    m = "mode#{i}"
    r.report(m) do
      100.times do
        res[m] = arr.send(m).inspect
      end
    end
  end
  res.each { |k, v| puts "%10s = %s" % [k, v] }
end

И вот вывод из прогона образца.

                                user     system      total        real
mode1                          34.375000   0.000000  34.375000 ( 34.393000)
mode2                           0.359000   0.000000   0.359000 (  0.359000)
mode3                           0.219000   0.000000   0.219000 (  0.219000)
     mode1 = 41
     mode2 = 41
     mode3 = [[41, 17], [80, 17], [72, 17]]

"Оптимизированный" режим3 занял 60% времени предыдущего владельца записи. Также обратите внимание на несколько записей с высокой частотой.

ИЗМЕНИТЬ

Несколько месяцев спустя, я заметил ответ Nilesh, который предложил следующее:

def mode4
  group_by{|i| i}.max{|x,y| x[1].length <=> y[1].length}[0]
end

Он не работает с 1.8.6 из коробки, потому что в этой версии нет Array # group_by. У ActiveSupport это, для разработчиков Rails, хотя кажется примерно на 2-3% медленнее, чем режим3 выше. Использование (превосходного) backports драгоценного камня, тем не менее, дает выигрыш в 10-12%, а также доставку целой кучи 1,8. 7 и 1.9.

Вышеупомянутое относится только к 1,8.6 - и, главным образом, только если оно установлено в Windows. Поскольку у меня он установлен, вот что вы получаете от IronRuby 1.0 (на .NET 4.0):

==========================   IronRuby   =====================================
(iterations bumped to **1000**)    user     system      total        real
mode1 (I didn't bother :-))
mode2                           4.265625   0.046875   4.312500 (  4.203151)
mode3                           0.828125   0.000000   0.828125 (  0.781255)
mode4                           1.203125   0.000000   1.203125 (  1.062507)

Таким образом, если производительность является суперкритической, сравните параметры в вашей версии Ruby и ОС. YMMV.

Ответ 3

array.max_by { |i| array.count(i) }

Ответ 4

Майк: Я нашел более быстрый метод. Попробуйте следующее:

  class Array
    def mode4
      group_by{|i| i}.max{|x,y| x[1].length <=> y[1].length}[0]
    end
  end

Выход Benchmark:

                                    user     system      total        real
mode1                          24.340000   0.070000  24.410000 ( 24.526991)
mode2                           0.200000   0.000000   0.200000 (  0.195348)
mode3                           0.120000   0.000000   0.120000 (  0.118200)
mode4                           0.050000   0.010000   0.060000 (  0.056315)
     mode1 = 76
     mode2 = 76
     mode3 = [[76, 18]]
     mode4 = 76

Ответ 5

arr = [ 1, 3, 44, 3 ]
most_frequent_item = arr.uniq.max_by{ |i| arr.count( i ) }
puts most_frequent_item
#=> 3

Не нужно даже думать о частотных отображениях.

Ответ 6

Это дубликат этого вопроса: Ruby - уникальные элементы в массиве

Вот решение вопроса:

group_by { |n| n }.values.max_by(&:size).first

Эта версия кажется еще быстрее, чем ответ Nilesh C. Вот код, который я использовал для сравнения (OS X 10.6 Core 2 2.4GHz MB).

Престижность Майк Вудхаус за (оригинальный) бенчмаркинг:

class Array
   def mode1
     group_by { |n| n }.values.max_by(&:size).first
   end
   def mode2
     freq = inject(Hash.new(0)) { |h,v| h[v] += 1; h }
     max = freq.values.max                   # we're only interested in the key(s) with the highest frequency
     freq.select { |k, f| f == max }         # extract the keys that have the max frequency
   end
end

arr = Array.new(1_0000) { |i| rand(100000) }    # something to test with

Benchmark.bm(30) do |r|
    (1..2).each do |i| r.report("mode#{i}") { 100.times do arr.send("mode#{i}").inspect; end }; end
end

И вот результаты теста:

                                user     system      total        real
mode1                           1.830000   0.010000   1.840000 (  1.876642)
mode2                           2.280000   0.010000   2.290000 (  2.382117)
 mode1 = 70099
 mode2 = [[70099, 3], [70102, 3], [51694, 3], [49685, 3], [38410, 3], [90815, 3], [30551, 3], [34720, 3], [58373, 3]]

Как вы можете видеть, эта версия примерно на 20% быстрее с учетом игнорирования связей. Мне также нравится лаконичность, я лично использую ее как есть без обезьян, патчирующих повсюду.:)

Ответ 7

если вы пытаетесь избежать изучения #inject (который вы не должны делать...)

words = ['cat', 'dog', 'snake', 'dog']
count = Hash.new(0)

words.each {|word| count[word] += 1}
count.sort_by { |k,v| v }.last

но если я прочитаю этот ответ раньше, теперь я ничего не знаю о #inject и man, вам нужно знать о #inject.

Ответ 8

idx = {}
[2,2,1,3,1].each { |i| idx.include?(i) ? idx[i] += 1 : idx[i] = 1}

Это просто простой индекс. Вы можете заменить массив [2,2,1..] любым символьным/строковым идентификатором, это не сработает с объектами, вам нужно будет ввести немного сложнее, но это достаточно просто.

перечитывая ваши вопросы, это решение немного перепроектировано, поскольку оно собирается вернуть вам индекс всех вхождений, а не только тот, который больше всего работает.

Ответ 9

Здесь другая версия, которая дает вам связи как режим, должна:

def mode
  group_by {|x| x}.group_by {|k,v| v.size}.sort.last.last.map(&:first)
end

Другими словами, группируйте значения, затем группируйте эти пары kv по числу значений, затем сортируйте эти kv-пары, возьмите последнюю (самую высокую) размерную группу и затем размотайте ее значения. Мне нравится group_by.

Ответ 10

def mode(array)

    count = []  # Number of times element is repeated in array
    output = [] 
    array.compact!
    unique = array.uniq
    j=0

    unique.each do |i|
        count[j] = array.count(i)
        j+=1
    end
    k=0
    count.each do |i|
        output[k] = unique[k] if i == count.max
        k+=1
    end  

    return output.compact.inspect
end

p mode([3,3,4,5]) #=> [3]

p mode([1,2,3]) #=> [1,2,3]

p mode([0,0,0,0,0,1,2,3,3,3,3,3]) #=> [0,3]

p mode([-1,-1,nil,nil,nil,0]) #=> [-1]

p mode([-2,-2,3,4,5,6,7,8,9,10,1000]) #=> [-2]

Ответ 11

В версиях Ruby> = 2.7 будет Enumerable # tally

Подсчитывает коллекцию. Возвращает хеш, где ключи являются элементами и значения являются номерами элементов в коллекции, которые соответствуют ключу.

Итак, вы можете сделать

[1, 1, 1, 2, 3].tally
# => {1=>3, 2=>1, 3=>1}