Ruby: ¿Cómo encontrar el elemento en la matriz que tiene la mayor cantidad de apariciones?

[1, 1, 1, 2, 3].mode => 1 ['cat', 'dog', 'snake', 'dog'].mode => dog 

Primero construya un hash mapeando cada valor en la matriz a su frecuencia …

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

… luego usa la tabla de frecuencias para encontrar el elemento con la frecuencia más alta:

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

Mientras adoro la solución grep por su elegancia y por recordarme (o enseñarme) acerca de un método en Enumerable que había olvidado (o pasado por alto por completo), es lento, lento, lento. Acepto al 100% que la creación del método de Array#mode es una buena idea, sin embargo, esto es Ruby, no necesitamos una biblioteca de funciones que actúen sobre matrices, podemos crear una mezcla que agregue las funciones necesarias a la clase Array sí mismo.

Pero la alternativa de inyección (Hash) usa una ordenación, que tampoco necesitamos realmente: solo queremos el valor con mayor ocurrencia.

Ninguna de las soluciones aborda la posibilidad de que más de un valor sea el modo. Tal vez eso no es un problema en el problema como se dijo (no se puede decir). Creo que me gustaría saber si hubo empate, y de todos modos, creo que podemos mejorar un poco el rendimiento.

 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 

Y aquí está el resultado de una ejecución de muestra.

  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]] 

El modo “optimizado” 3 tomó el 60% del tiempo del poseedor del registro anterior. Tenga en cuenta también las entradas múltiples de frecuencia más alta.

EDITAR

Unos meses después, noté la respuesta de Nilesh , que ofrecía esto:

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

No funciona con 1.8.6 de fábrica, porque esa versión no tiene Array # group_by. ActiveSupport lo tiene, para los desarrolladores de Rails, aunque parece un 2-3% más lento que el modo 3 anterior. Sin embargo, el uso de la (excelente) joya de backports produce una ganancia del 10-12%, además de entregar una stack completa de 1.8.7 y 1.9 extras.

Lo anterior se aplica únicamente a 1.8.6 , y principalmente solo si está instalado en Windows. Desde que lo tengo instalado, esto es lo que obtienes de IronRuby 1.0 (en .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) 

Entonces, en caso de que el rendimiento sea súper crítico, compare las opciones en su versión de Ruby & SO. YMMV .

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

Mike: encontré un método más rápido. Prueba esto:

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

El resultado de 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 
 arr = [ 1, 3, 44, 3 ] most_frequent_item = arr.uniq.max_by{ |i| arr.count( i ) } puts most_frequent_item #=> 3 

No es necesario ni siquiera pensar en mapeos de frecuencia.

Este es un duplicado de esta pregunta: Ruby – Elementos únicos en Array

Aquí está la solución de la pregunta:

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

Esa versión parece ser incluso más rápida que la respuesta de Nilesh C. Aquí está el código que usé para compararlo (OS X 10.6 Core 2 2.4GHz MB).

Felicitaciones a Mike Woodhouse por el código de referencia (original):

 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 

Y aquí están los resultados del benchmark:

  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]] 

Como puede ver, esta versión es aproximadamente un 20% más rápida con la advertencia de ignorar vínculos. También me gusta la brevedad, personalmente la uso tal como está sin parche de mono por todas partes. 🙂

si intentas evitar el aprendizaje de #inject (que no debes hacer …)

 words = ['cat', 'dog', 'snake', 'dog'] count = Hash.new(0) words.each {|word| count[word] += 1} count.sort_by { |k,v| v }.last 

pero si leo esta respuesta antes, ahora no sabría nada sobre #inject y man, necesitas saber sobre #inject.

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

Esto es solo un indexador simple. Podría reemplazar la matriz [2,2,1 ..] con cualquier tipo de identificador basado en símbolo / cadena, esto no funcionaría con los objetos, tendría que introducir un poco más de complejidad, pero esto es bastante simple.

Al releer sus preguntas, esta solución está un poco sobre-diseñada ya que le devolverá un índice de todas las ocurrencias, no solo la que tiene más.

Aquí hay otra versión que te da los lazos como un modo debe:

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

En otras palabras, agrupe los valores, luego agrupe esos pares de kv por el número de valores, luego clasifique los pares de kv, tome el último (más alto) grupo de tamaño y luego desenrolle sus valores. Me gusta group_by .

 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]