Подтвердить что ты не робот

Как подсчитать одинаковые строковые элементы в массиве Ruby

У меня есть следующий 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}

4b9b3361

Ответ 1

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = Hash.new(0)
names.each { |name| counts[name] += 1 }
# => {"Jason" => 2, "Teresa" => 1, ....

Ответ 2

names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}

дает вам

{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

Ответ 3

Ruby v2. 4+ (текущий)

Следующий код был невозможен в стандартном ruby, когда этот вопрос был впервые задан (февраль 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) есть несколько способов решить эту проблему, но (на мой взгляд) не существует четкого "лучшего" способа. Смотрите другие ответы на этот пост.


(Февраль 2019 г.) Редактировать:

Ruby v2. 7+ (еще не выпущен)

Считайте этот комментарий заполнителем для будущего; Я 2.7.0 этот пост, когда 2.7.0 ruby 2.7.0 (ожидается в декабре 2019 года), чтобы подтвердить, что метод работает на базовом языке.

Там было недавнее улучшение языка. Если все пойдет по плану, мы должны ожидать появления нового метода Enumerable#tally, добавленного в ruby v2.7.0. Этот метод добавляет новый синтаксис специально для этой проблемы:

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.tally
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ответ 4

Теперь, используя 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}

Ответ 5

На самом деле существует структура данных, которая делает это: MultiSet.

К сожалению, в основной библиотеке Ruby или стандартной библиотеке нет реализации MultiSet, но есть несколько реализаций, плавающих по сети.

Это отличный пример того, как выбор структуры данных может упростить алгоритм. Фактически, в этом конкретном примере алгоритм даже полностью уходит. Это буквально просто:

Multiset.new(*names)

И что это. Пример, используя 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'>

Ответ 6

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}

Ответ 7

Ниже приведен немного более функциональный стиль программирования:

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]]

Ответ 8

Это работает.

arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
result = {}
arr.uniq.each{|element| result[element] = arr.count(element)}

Ответ 9

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}

Кредит Frank Wambutt

Ответ 10

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}

Ответ 11

Здесь много больших реализаций.

Но как новичок я бы счел это самым легким для чтения и реализации

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

Это может быть немного более многословным (и с учетом производительности вы будете делать ненужную работу с переопределяющими клавишами), но, на мой взгляд, легче читать и понимать, чего вы хотите достичь

Ответ 12

Это скорее комментарий, чем ответ, но комментарий не оправдал бы его. Если вы выполняете 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 - класс.

Ответ 13

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 миллисекунды

:)

Ответ 14

Рубин 2. 7+

Ruby 2.7 представляет Enumerable#tally для этой конкретной цели. Здесь хорошее резюме здесь.

В этом случае использования:

array.tally
# => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }

Документы на выпускаемые функции находятся здесь.

Надеюсь, это поможет кому-то!