Tengo lo siguiente Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
¿Cómo produzco un recuento para cada elemento idéntico ?
Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?
o producir un hachís Donde:
Donde: hash = {"Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1}
Enumerable#tally
. Más info aquí .Respuestas:
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = Hash.new(0) names.each { |name| counts[name] += 1 } # => {"Jason" => 2, "Teresa" => 1, ....
fuente
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}
te dio
{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
fuente
each_with_object
lugar deinject
, no tiene que regresar (;total
) en el bloque.array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
names.tally
.Ruby v2.7 + (más reciente)
A partir de ruby v2.7.0 (lanzado en diciembre de 2019), el lenguaje principal ahora incluye
Enumerable#tally
un nuevo método diseñado específicamente para este problema:names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] names.tally #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Ruby v2.4 + (actualmente compatible, pero más antiguo)
El siguiente código no era posible en ruby estándar cuando se hizo esta pregunta por primera vez (febrero de 2011), ya que usa:
Object#itself
, que se agregó a Ruby v2.2.0 (lanzado en diciembre de 2014).Hash#transform_values
, que se agregó a Ruby v2.4.0 (lanzado en diciembre de 2016).Estas modernas adiciones a Ruby permiten la siguiente implementación:
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] names.group_by(&:itself).transform_values(&:count) #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Ruby v2.2 + (obsoleto)
Si usa una versión anterior de Ruby, sin acceso al
Hash#transform_values
método mencionado anteriormente , puede usarArray#to_h
, que se agregó a Ruby v2.1.0 (lanzado en diciembre de 2013):names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Para versiones aún más antiguas de ruby (
<= 2.1
), hay varias formas de resolver esto, pero (en mi opinión) no existe una "mejor" forma clara. Vea las otras respuestas a esta publicación.fuente
count
lugar desize
/length
?Array#size
yArray#length
,Array#count
puede tomar un argumento o bloque opcional; pero si se usa con ninguno, entonces su implementación es idéntica. Más específicamente, los tres métodos llamanLONG2NUM(RARRAY_LEN(ary))
bajo el capó: recuento / longitud.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
sort_by{ |k, v| -v}
, ¡no esreverse
necesario! ;-)Ahora, con Ruby 2.2.0 puede aprovechar el
itself
método .names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = {} names.group_by(&:itself).each { |k,v| counts[k] = v.length } # counts > {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
fuente
Hash#transform_values
que nos permite simplificar su código aún más:names.group_by(&:itself).transform_values(&:count)
Array#to_h
, que se agregó a Ruby v2.1.0 (lanzado en diciembre de 2013, es decir, casi 3 años después de la pregunta original se preguntó!)De hecho, hay una estructura de datos que hace esto:
MultiSet
.Desafortunadamente, no hay
MultiSet
implementación en la biblioteca central de Ruby o en la biblioteca estándar, pero hay un par de implementaciones flotando en la web.Este es un gran ejemplo de cómo la elección de una estructura de datos puede simplificar un algoritmo. De hecho, en este ejemplo en particular, el algoritmo incluso desaparece por completo . Es literalmente solo:
Y eso es. Ejemplo, usando https://GitHub.Com/Josh/Multimap/ :
require 'multiset' names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison] histogram = Multiset.new(*names) # => #<Multiset: {"Jason", "Jason", "Teresa", "Judah", "Judah", "Judah", "Michelle", "Allison"}> histogram.multiplicity('Judah') # => 3
Ejemplo, usando http://maraigue.hhiro.net/multiset/index-en.php :
require 'multiset' names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison] histogram = Multiset[*names] # => #<Multiset:#2 'Jason', #1 'Teresa', #3 'Judah', #1 'Michelle', #1 'Allison'>
fuente
Multiset
se rige por reglas matemáticas estrictas y soporta las operaciones típicas de conjuntos (unión, intersección, complemento, ...) de una manera que es mayormente consistente con los axiomas, leyes y teoremas de la teoría matemática de conjuntos "normal", aunque algunas leyes importantes lo hacen no se mantenga cuando intente generalizarlos a conjuntos múltiples. Pero eso está mucho más allá de mi comprensión del asunto. Los uso como una estructura de datos de programación, no como un concepto matemático.{A, A, B} = {A, B}
. ¡Esto es claramente una violación de la definición misma de conjuntos múltiples!Enumberable#each_with_object
te evita devolver el hash final.names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }
Devoluciones:
=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
fuente
each_with_object
variante es más legible para mí queinject
Ruby 2.7+
Ruby 2.7 se presenta
Enumerable#tally
con este propósito exacto. Hay un buen resumen aquí .En este caso de uso:
array.tally # => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }
Los documentos sobre las funciones que se lanzarán están aquí .
¡Espero que esto ayude a alguien!
fuente
Esto funciona.
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] result = {} arr.uniq.each{|element| result[element] = arr.count(element)}
fuente
O(n^2)
(lo que importará para algunos valores den
) y hace un trabajo extra (¡tiene que contar para "Judá" 3x, por ejemplo) !. También sugeriría eneach
lugar demap
(el resultado del mapa se descarta)El siguiente es un estilo de programación un poco más funcional:
array_with_lower_case_a = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] hash_grouped_by_name = array_with_lower_case_a.group_by {|name| name} hash_grouped_by_name.map{|name, names| [name, names.length]} => [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]]
Una ventaja
group_by
es que puede usarlo para agrupar elementos equivalentes pero no exactamente idénticos:another_array_with_lower_case_a = ["Jason", "jason", "Teresa", "Judah", "Michelle", "Judah Ben-Hur", "JUDAH", "Allison"] hash_grouped_by_first_name = another_array_with_lower_case_a.group_by {|name| name.split(" ").first.capitalize} hash_grouped_by_first_name.map{|first_name, names| [first_name, names.length]} => [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]]
fuente
a = [1, 2, 3, 2, 5, 6, 7, 5, 5] a.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 } # => {1=>1, 2=>2, 3=>1, 5=>3, 6=>1, 7=>1}
Crédito Frank Wambutt
fuente
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] Hash[names.group_by{|i| i }.map{|k,v| [k,v.size]}] # => {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
fuente
Muchas implementaciones geniales aquí.
Pero como principiante, consideraría que este es el más fácil de leer e implementar.
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] name_frequency_hash = {} names.each do |name| count = names.count(name) name_frequency_hash[name] = count end #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Los pasos que dimos:
names
matriznames
matrizname
y un valor usando elcount
Puede ser un poco más detallado (y en términos de rendimiento, estará haciendo un trabajo innecesario con teclas anuladas), pero en mi opinión, es más fácil de leer y comprender para lo que desea lograr.
fuente
Hash.new(0)
. El más cercano al pseudocódigo. Eso puede ser bueno para la legibilidad, pero también hacer un trabajo innecesario puede dañar la legibilidad para los lectores que lo notan porque en casos más complejos pasarán un poco de tiempo pensando que se están volviendo locos tratando de averiguar por qué se hace.Esto es más un comentario que una respuesta, pero un comentario no le haría justicia. Si lo hace
Array = foo
, bloqueará al menos una implementación de IRB:C:\Documents and Settings\a.grimm>irb irb(main):001:0> Array = nil (irb):1: warning: already initialized constant Array => nil C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3177:in `rl_redisplay': undefined method `new' for nil:NilClass (NoMethodError) from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3873:in `readline_internal_setup' from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4704:in `readline_internal' from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4727:in `readline' from C:/Ruby19/lib/ruby/site_ruby/1.9.1/readline.rb:40:in `readline' from C:/Ruby19/lib/ruby/1.9.1/irb/input-method.rb:115:in `gets' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:139:in `block (2 levels) in eval_input' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:271:in `signal_status' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:138:in `block in eval_input' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `call' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `buf_input' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:103:in `getc' from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:205:in `match_io' from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:75:in `match' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:287:in `token' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:263:in `lex' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:234:in `block (2 levels) in each_top_level_statement' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `loop' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `block in each_top_level_statement' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `catch' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `each_top_level_statement' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:153:in `eval_input' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:70:in `block in start' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `catch' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `start' from C:/Ruby19/bin/irb:12:in `<main>' C:\Documents and Settings\a.grimm>
Eso es porque
Array
es una clase.fuente
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}
Tiempo transcurrido 0,028 milisegundos
Curiosamente, la implementación de stupidgeek evaluó:
Tiempo transcurrido 0,041 milisegundos
y la respuesta ganadora:
Tiempo transcurrido 0.011 milisegundos
:)
fuente