У меня есть следующие Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
Как произвести подсчет каждого идентичного элемента ?
Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?
или создать хеш Где:
Где: hash = {"Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1}
Enumerable#tally
. Больше информации здесь .Ответы:
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = Hash.new(0) names.each { |name| counts[name] += 1 } # => {"Jason" => 2, "Teresa" => 1, ....
источник
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}
дает тебе
{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
источник
each_with_object
вместо,inject
вам не нужно возвращать (;total
) в блоке.array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
names.tally
.Ruby v2.7 + (последняя версия)
Начиная с версии ruby v2.7.0 (выпущенной в декабре 2019 г.), основной язык теперь включает
Enumerable#tally
- новый метод , разработанный специально для этой проблемы:names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] names.tally #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Ruby v2.4 + (в настоящее время поддерживается, но старше)
Следующий код был невозможен в стандартном рубине, когда этот вопрос был впервые задан (февраль 2011 г.), поскольку он использует:
Object#itself
, который был добавлен в Ruby v2.2.0 (выпущен в декабре 2014 г.).Hash#transform_values
, который был добавлен в Ruby v2.4.0 (выпущен в декабре 2016 г.).Эти современные дополнения к Ruby позволяют реализовать следующую реализацию:
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 + (устарело)
Если вы используете старую версию Ruby без доступа к вышеупомянутому
Hash#transform_values
методу, вы можете вместо этого использоватьArray#to_h
, который был добавлен в Ruby v2.1.0 (выпущен в декабре 2013 г.):names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Для даже более старых версий Ruby (
<= 2.1
) есть несколько способов решить эту проблему, но (на мой взгляд) нет однозначного «лучшего» способа. Смотрите другие ответы на этот пост.источник
count
вместоsize
/length
?Array#size
иArray#length
,Array#count
может принимать необязательный аргумент или блок; но если не используется ни один из них, то его реализация идентична. В частности, все три метода вызываютLONG2NUM(RARRAY_LEN(ary))
под капотом: count / length.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
sort_by{ |k, v| -v}
, не надоreverse
! ;-)Теперь, используя Ruby 2.2.0, вы можете использовать этот
itself
метод .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}
источник
Hash#transform_values
который позволяет нам еще больше упростить ваш код:names.group_by(&:itself).transform_values(&:count)
Array#to_h
- который был добавлен в Ruby v2.1.0 (выпущен в декабре 2013 года, то есть почти через 3 года после исходного вопроса спросили!)На самом деле есть структура данных, которая делает это:
MultiSet
.К сожалению, нет
MultiSet
в основной или стандартной библиотеке Ruby реализации, но в сети есть несколько реализаций.Это отличный пример того, как выбор структуры данных может упростить алгоритм. Фактически, в этом конкретном примере алгоритм даже полностью уходит. Буквально просто:
Вот и все. Пример с использованием 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
Пример с использованием 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'>
источник
Multiset
управляется строгими математическими правилами и поддерживает типичные операции над множеством (объединение, пересечение, дополнение, ...) способом, который в основном согласуется с аксиомами, законами и теоремами "нормальной" математической теории множеств, хотя некоторые важные законы это делают не выполняются, когда вы пытаетесь обобщить их на мультимножества. Но это выходит за рамки моего понимания этого вопроса. Я использую их как структуру данных программирования, а не как математическую концепцию.{A, A, B} = {A, B}
. Это явно нарушение самого определения множественных наборов!Enumberable#each_with_object
избавляет вас от возврата окончательного хеша.names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }
Возврат:
=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
источник
each_with_object
вариант для меня более читабельный, чемinject
Рубин 2.7+
Ruby 2.7 вводится именно
Enumerable#tally
для этой цели. Там хорошее резюме здесь .В этом случае использования:
array.tally # => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }
Документация о выпускаемых функциях находится здесь .
Надеюсь, это кому-то поможет!
источник
Это работает.
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] result = {} arr.uniq.each{|element| result[element] = arr.count(element)}
источник
O(n^2)
(что будет иметь значение для некоторых значенийn
) и выполняет дополнительную работу (например, для «Иудеи» 3х) !. Я бы также предложилeach
вместоmap
(результат карты отбрасывается)Ниже приводится чуть более функциональный стиль программирования:
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]]
Одним из преимуществ
group_by
является то, что вы можете использовать его для группировки эквивалентных, но не совсем идентичных элементов: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]]
источник
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}
Кредит Фрэнк Вамбутт
источник
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}
источник
Здесь много отличных реализаций.
Но как новичок я бы счел это самым простым для чтения и реализации.
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}
Что мы сделали:
names
массивnames
массивеname
и значение, используяcount
Это может быть немного более подробным (и с точки зрения производительности вы будете выполнять некоторую ненужную работу с переопределением ключей), но, на мой взгляд, легче читать и понимать, чего вы хотите достичь.
источник
Hash.new(0)
. Самый близкий к псевдокоду. Это может быть полезно для удобочитаемости, но также выполнение ненужной работы может повредить читаемости для читателей, которые это заметят, потому что в более сложных случаях они потратят немного времени, думая, что сходят с ума, пытаясь понять, почему это делается.Это больше комментарий, чем ответ, но комментарий не воздает ему должного. Если вы это сделаете
Array = foo
, вы откажетесь по крайней мере от одной реализации 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>
Потому что
Array
это класс.источник
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}
Прошедшее время 0,028 миллисекунды
Интересно, что реализация stupidgeek проверила:
Прошедшее время 0,041 миллисекунды
и победивший ответ:
Прошедшее время 0,011 миллисекунды
:)
источник