Как посчитать повторяющиеся элементы в массиве Ruby - PullRequest
62 голосов
/ 20 февраля 2009

У меня есть отсортированный массив:

[
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="There is insufficient system memory to run this query.">'
]

Я бы хотел получить что-то подобное, но это не обязательно должен быть хеш:

[
  {:error => 'FATAL <error title="Request timed out.">', :count => 2},
  {:error => 'FATAL <error title="There is insufficient system memory to run this query.">', :count => 1}
]

Ответы [ 11 ]

122 голосов
/ 20 февраля 2009

Следующий код печатает то, что вы просили. Я дам вам решить, как на самом деле использовать для генерации хеша, который вы ищете:

# sample array
a=["aa","bb","cc","bb","bb","cc"]

# make the hash default to 0 so that += will work correctly
b = Hash.new(0)

# iterate over the array, counting duplicate entries
a.each do |v|
  b[v] += 1
end

b.each do |k, v|
  puts "#{k} appears #{v} times"
end

Примечание: Я только что заметил, что вы сказали, что массив уже отсортирован. Приведенный выше код не требует сортировки. Использование этого свойства может привести к более быстрому коду.

68 голосов
/ 21 февраля 2009

Вы можете сделать это очень кратко (одна строка), используя inject:

a = ['FATAL <error title="Request timed out.">',
      'FATAL <error title="Request timed out.">',
      'FATAL <error title="There is insufficient ...">']

b = a.inject(Hash.new(0)) {|h,i| h[i] += 1; h }

b.to_a.each {|error,count| puts "#{count}: #{error}" }

Будет производить:

1: FATAL <error title="There is insufficient ...">
2: FATAL <error title="Request timed out.">
29 голосов
/ 28 мая 2014

Если у вас есть такой массив:

words = ["aa","bb","cc","bb","bb","cc"]

, где вам нужно сосчитать повторяющиеся элементы, решение в одну строку:

result = words.each_with_object(Hash.new(0)) { |word,counts| counts[word] += 1 }
15 голосов
/ 24 января 2017

Другой подход к ответам выше, использующий Enumerable # group_by .

[1, 2, 2, 3, 3, 3, 4].group_by(&:itself).map { |k,v| [k, v.count] }.to_h
# {1=>1, 2=>2, 3=>3, 4=>1}

Разбивая это на различные вызовы методов:

a = [1, 2, 2, 3, 3, 3, 4]
a = a.group_by(&:itself) # {1=>[1], 2=>[2, 2], 3=>[3, 3, 3], 4=>[4]}
a = a.map { |k,v| [k, v.count] } # [[1, 1], [2, 2], [3, 3], [4, 1]]
a = a.to_h # {1=>1, 2=>2, 3=>3, 4=>1}

Enumerable#group_by был добавлен в Ruby 1.8.7.

12 голосов
/ 05 апреля 2017

Как насчет следующего:

things = [1, 2, 2, 3, 3, 3, 4]
things.uniq.map{|t| [t,things.count(t)]}.to_h

Это выглядит чище и более наглядно описывает то, что мы на самом деле пытаемся сделать.

Я подозреваю, что это также будет работать лучше с большими коллекциями, чем те, которые перебирают каждое значение.

Тест производительности производительности:

a = (1...1000000).map { rand(100)}
                       user     system      total        real
inject                 7.670000   0.010000   7.680000 (  7.985289)
array count            0.040000   0.000000   0.040000 (  0.036650)
each_with_object       0.210000   0.000000   0.210000 (  0.214731)
group_by               0.220000   0.000000   0.220000 (  0.218581)

Так что это немного быстрее.

8 голосов
/ 04 мая 2012

Лично я бы сделал это так:

# myprogram.rb
a = ['FATAL <error title="Request timed out.">',
'FATAL <error title="Request timed out.">',
'FATAL <error title="There is insufficient system memory to run this query.">']
puts a

Затем запустите программу и направьте ее в uniq -c:

ruby myprogram.rb | uniq -c

Выход:

 2 FATAL <error title="Request timed out.">
 1 FATAL <error title="There is insufficient system memory to run this query.">
5 голосов
/ 24 сентября 2018

Из Ruby> = 2.2 вы можете использовать itself: array.group_by(&:itself).transform_values(&:count)

Более подробно:

array = [
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="There is insufficient system memory to run this query.">'
];

array.group_by(&:itself).transform_values(&:count)
 => { "FATAL <error title=\"Request timed out.\">"=>2,
      "FATAL <error title=\"There is insufficient system memory to run this query.\">"=>1 }
3 голосов
/ 20 февраля 2009
a = [1,1,1,2,2,3]
a.uniq.inject([]){|r, i| r << { :error => i, :count => a.select{ |b| b == i }.size } }
=> [{:count=>3, :error=>1}, {:count=>2, :error=>2}, {:count=>1, :error=>3}]
1 голос
/ 28 июля 2018

Если вы хотите использовать это часто, я предлагаю сделать это:

# lib/core_extensions/array/duplicates_counter
module CoreExtensions
  module Array
    module DuplicatesCounter
      def count_duplicates
        self.each_with_object(Hash.new(0)) { |element, counter| counter[element] += 1 }.sort_by{|k,v| -v}.to_h
      end
    end
  end
end

Загрузите его с

Array.include CoreExtensions::Array::DuplicatesCounter

А затем используйте откуда угодно только с:

the_ar = %w(a a a a a a a  chao chao chao hola hola mundo hola chao cachacho hola)
the_ar.duplicates_counter
{
           "a" => 7,
        "chao" => 4,
        "hola" => 4,
       "mundo" => 1,
    "cachacho" => 1
}
0 голосов
/ 13 ноября 2012

Вот пример массива:

a=["aa","bb","cc","bb","bb","cc"]
  1. Выберите все уникальные ключи.
  2. Для каждого ключа мы собираем их в хеш, чтобы получить что-то вроде этого: {'bb' => ['bb', 'bb']}
    res = a.uniq.inject({}) {|accu, uni| accu.merge({ uni => a.select{|i| i == uni } })}
    {"aa"=>["aa"], "bb"=>["bb", "bb", "bb"], "cc"=>["cc", "cc"]}

Теперь вы можете делать такие вещи, как:

res['aa'].size 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...