Eu tenho o seguinte Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
Como faço para produzir uma contagem para cada elemento idêntico ?
Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?
ou produzir um hash Onde:
Onde: hash = {"Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1}
Enumerable#tally
. Mais informações aqui .Respostas:
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = Hash.new(0) names.each { |name| counts[name] += 1 } # => {"Jason" => 2, "Teresa" => 1, ....
fonte
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}
da-te
{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
fonte
each_with_object
vez deinject
, não precisará retornar (;total
) no bloco.array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
names.tally
.Ruby v2.7 + (mais recente)
A partir do ruby v2.7.0 (lançado em dezembro de 2019), a linguagem central agora inclui
Enumerable#tally
- um novo método , projetado especificamente 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 + (atualmente compatível, mas mais antigo)
O código a seguir não era possível no ruby padrão quando esta pergunta foi feita pela primeira vez (fevereiro de 2011), pois usa:
Object#itself
, que foi adicionado ao Ruby v2.2.0 (lançado em dezembro de 2014).Hash#transform_values
, que foi adicionado ao Ruby v2.4.0 (lançado em dezembro de 2016).Essas adições modernas ao Ruby permitem a seguinte implementação:
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)
Se estiver usando uma versão mais antiga do Ruby, sem acesso ao
Hash#transform_values
método mencionado acima , você pode usar oArray#to_h
, que foi adicionado ao Ruby v2.1.0 (lançado em dezembro 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 versões de ruby ainda mais antigas (
<= 2.1
), existem várias maneiras de resolver isso, mas (na minha opinião) não existe uma "melhor" maneira bem definida. Veja as outras respostas a esta postagem.fonte
count
vez desize
/length
?Array#size
eArray#length
,Array#count
pode receber um argumento ou bloco opcional; mas se usado com nenhum, sua implementação é idêntica. Mais especificamente, todos os três métodos chamamLONG2NUM(RARRAY_LEN(ary))
sob o capô: contagem / comprimento.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
sort_by{ |k, v| -v}
, nãoreverse
precisa! ;-)Agora, usando Ruby 2.2.0, você pode aproveitar o
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}
fonte
Hash#transform_values
que nos permite simplificar seu código ainda mais:names.group_by(&:itself).transform_values(&:count)
Array#to_h
- que foi adicionado ao Ruby v2.1.0 (lançado em dezembro de 2013 - ou seja, quase 3 anos após a pergunta original foi perguntado!)Na verdade, há uma estrutura de dados que faz isso:
MultiSet
.Infelizmente, não há
MultiSet
implementação na biblioteca central ou biblioteca padrão do Ruby, mas existem algumas implementações flutuando pela web.Este é um ótimo exemplo de como a escolha de uma estrutura de dados pode simplificar um algoritmo. Na verdade, neste exemplo específico, o algoritmo desaparece completamente . É literalmente apenas:
E é isso. Exemplo, 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
Exemplo, 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'>
fonte
Multiset
é governado por regras matemáticas estritas e suporta as operações de conjunto típicas (união, interseção, complemento, ...) de uma forma que é principalmente consistente com os axiomas, leis e teoremas da teoria matemática de conjuntos "normal", embora algumas leis importantes o façam não se mantém quando você tenta generalizá-los para multisets. Mas isso está muito além da minha compreensão do assunto. Eu os uso como uma estrutura de dados de programação, não como um conceito matemático.{A, A, B} = {A, B}
. Isso é claramente uma violação da própria definição de multi-conjuntos!Enumberable#each_with_object
evita que você retorne o hash final.names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }
Retorna:
=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
fonte
each_with_object
variante é mais legível para mim do queinject
Ruby 2.7+
Ruby 2.7 está apresentando
Enumerable#tally
exatamente para esse propósito. Há um bom resumo aqui .Neste caso de uso:
array.tally # => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }
Os documentos sobre os recursos que estão sendo lançados estão aqui .
Espero que isso ajude alguém!
fonte
Isso funciona.
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] result = {} arr.uniq.each{|element| result[element] = arr.count(element)}
fonte
O(n^2)
(o que importará para alguns valores den
) e faça um trabalho extra (tem que contar para "Judá" 3x, por exemplo) !. Eu também sugeriria emeach
vez demap
(o resultado do mapa está sendo descartado)O seguinte é um estilo de programação um pouco mais 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]]
Uma vantagem
group_by
é que você pode usá-lo para agrupar itens equivalentes, mas não exatamente 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]]
fonte
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
fonte
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}
fonte
Muitas implementações excelentes aqui.
Mas como um iniciante, eu consideraria isso o mais fácil de ler 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}
As etapas que demos:
names
matriznames
matrizname
e um valor usando ocount
Pode ser um pouco mais detalhado (e em termos de desempenho, você fará algum trabalho desnecessário com as teclas de substituição), mas na minha opinião mais fácil de ler e entender para o que você deseja alcançar
fonte
Hash.new(0)
. O mais próximo do pseudocódigo. Isso pode ser bom para a legibilidade, mas também fazer um trabalho desnecessário pode prejudicar a legibilidade para os leitores que perceberem, porque em casos mais complexos, eles passarão algum tempo pensando que estão enlouquecendo tentando descobrir por que isso é feito.Este é mais um comentário do que uma resposta, mas um comentário não faria justiça. Se você fizer isso
Array = foo
, você travará pelo menos uma implementação do 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>
Isso porque
Array
é uma classe.fonte
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}
Tempo decorrido 0,028 milissegundos
curiosamente, a implementação de stupidgeek comparou:
Tempo decorrido 0,041 milissegundos
e a resposta vencedora:
Tempo decorrido 0,011 milissegundos
:)
fonte