objects - Ruby: ¿Cómo encontrar el elemento en la matriz que tiene la mayor cantidad de apariciones?
ruby select (10)
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
.
[1, 1, 1, 2, 3].mode
=> 1
[''cat'', ''dog'', ''snake'', ''dog''].mode
=> dog
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. :)
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 pila 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 .
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
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
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.
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.
array.max_by { |i| array.count(i) }
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]
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.