Mam następujące Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
Jak obliczyć liczbę dla każdego identycznego elementu ?
Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?
lub stwórz hash Gdzie:
Gdzie: hash = {"Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1}
Enumerable#tally
. Więcej informacji tutaj .Odpowiedzi:
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = Hash.new(0) names.each { |name| counts[name] += 1 } # => {"Jason" => 2, "Teresa" => 1, ....
źródło
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}
daje Ci
{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
źródło
each_with_object
zamiastinject
you, nie musisz zwracać (;total
) w bloku.array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
names.tally
.Ruby v2.7 + (najnowszy)
Począwszy od wersji 2.7.0 Ruby (wydanej w grudniu 2019 r.), Rdzeń języka zawiera teraz
Enumerable#tally
- nową metodę , zaprojektowaną specjalnie dla tego problemu:names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] names.tally #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Ruby v2.4 + (obecnie obsługiwane, ale starsze)
Poniższy kod nie był możliwy w standardowym ruby, gdy po raz pierwszy zadano to pytanie (luty 2011), ponieważ używa:
Object#itself
, który został dodany do Ruby v2.2.0 (wydany w grudniu 2014).Hash#transform_values
, który został dodany do Ruby v2.4.0 (wydany w grudniu 2016 r.).Te nowoczesne dodatki do Rubiego umożliwiają następującą implementację:
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 + (przestarzały)
Jeśli używasz starszej wersji Ruby, bez dostępu do wyżej wymienionej
Hash#transform_values
metody, możesz zamiast tego użyćArray#to_h
, który został dodany do Ruby v2.1.0 (wydany w grudniu 2013):names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
W przypadku nawet starszych wersji Ruby (
<= 2.1
) istnieje kilka sposobów rozwiązania tego problemu, ale (moim zdaniem) nie ma jednoznacznego, „najlepszego” sposobu. Zobacz inne odpowiedzi na ten post.źródło
count
zamiastsize
/length
?Array#size
iArray#length
,Array#count
może przyjmować opcjonalny argument lub blok; ale jeśli jest używany z żadnym z nich, jego implementacja jest identyczna. Dokładniej, wszystkie trzy metody wywołująLONG2NUM(RARRAY_LEN(ary))
pod maską: liczba / długość.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
sort_by{ |k, v| -v}
, niereverse
potrzeba! ;-)Teraz używając Ruby 2.2.0 możesz wykorzystać tę
itself
metodę .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}
źródło
Hash#transform_values
która pozwala nam jeszcze bardziej uprościć Twój kod:names.group_by(&:itself).transform_values(&:count)
Array#to_h
- co zostało dodane do Rubiego w wersji 2.1.0 (wydanej w grudniu 2013 - tj. Prawie 3 lata po pierwotnym pytaniu został zapytany!)Jest rzeczywiście struktura danych, która wykonuje to:
MultiSet
.Niestety, nie ma
MultiSet
implementacji w podstawowej bibliotece Ruby ani w standardowej bibliotece, ale jest kilka implementacji pływających po sieci.To doskonały przykład tego, jak wybór struktury danych może uprościć algorytm. W rzeczywistości w tym konkretnym przykładzie algorytm całkowicie znika. To dosłownie tylko:
I to wszystko. Przykład, używając 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
Przykład, używając 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'>
źródło
Multiset
rządzi się ścisłymi regułami matematycznymi i obsługuje typowe operacje na zbiorach (suma, przecięcie, dopełnienie, ...) w sposób, który jest w większości zgodny z aksjomatami, prawami i twierdzeniami "normalnej" matematycznej teorii mnogości, chociaż niektóre ważne prawa tak nie trzymaj się, gdy próbujesz uogólnić je na zestawy wielokrotne. Ale to przekracza moje rozumienie sprawy. Używam ich jako struktury danych programowania, a nie koncepcji matematycznej.{A, A, B} = {A, B}
. Jest to ewidentne naruszenie samej definicji multizestawów!Enumberable#each_with_object
oszczędza przed zwróceniem końcowego skrótu.names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }
Zwroty:
=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
źródło
each_with_object
wariant jest dla mnie bardziej czytelny niżinject
Ruby 2.7+
W
Enumerable#tally
tym właśnie celu wprowadza się Ruby 2.7 . Jest dobre podsumowanie tutaj .W tym przypadku:
array.tally # => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }
Dokumentacja dotycząca udostępnianych funkcji jest tutaj .
Mam nadzieję, że to komuś pomoże!
źródło
To działa.
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] result = {} arr.uniq.each{|element| result[element] = arr.count(element)}
źródło
O(n^2)
(co będzie miało znaczenie dla niektórych wartościn
) i wykonuje dodatkową pracę (na przykład musi liczyć się dla „Judy” 3x) !. Sugerowałbym równieżeach
zamiastmap
(wynik mapy jest odrzucany)Poniżej przedstawiono nieco bardziej funkcjonalny styl programowania:
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]]
Jedną z zalet
group_by
jest to, że można go użyć do grupowania równoważnych, ale nie dokładnie identycznych elementów: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]]
źródło
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}
Kredyt Frank Wambutt
źródło
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}
źródło
Wiele świetnych realizacji tutaj.
Ale jako początkujący uznałbym to za najłatwiejsze do odczytania i wdrożenia
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}
Kroki, które podjęliśmy:
names
tablicęnames
tablicyname
i wartość przy użyciucount
Może być nieco bardziej rozwlekły (i jeśli chodzi o wydajność, będziesz wykonywać niepotrzebną pracę z nadpisywaniem klawiszy), ale moim zdaniem łatwiejszy do odczytania i zrozumienia dla tego, co chcesz osiągnąć
źródło
Hash.new(0)
. Najbliższy pseudokodowi. Może to być dobre dla czytelności, ale wykonywanie niepotrzebnej pracy może zaszkodzić czytelnikom, którzy to zauważą, ponieważ w bardziej złożonych przypadkach spędzą trochę czasu na myśleniu, że oszaleli, próbując dowiedzieć się, dlaczego to się stało.To bardziej komentarz niż odpowiedź, ale komentarz nie byłby sprawiedliwy. Jeśli to zrobisz
Array = foo
, zawiesisz co najmniej jedną implementację 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>
To dlatego, że
Array
to klasa.źródło
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}
Upłynął czas 0,028 milisekund
co ciekawe, implementacja stupidgeeka została przetestowana pod kątem:
Upłynął czas 0,041 milisekundy
i zwycięska odpowiedź:
Upłynął czas 0,011 milisekund
:)
źródło