субота, 4 грудня 2010 р.

Робота з неіснуючими ключами в Hash

Веду цей блог для написання безґлуздих думок (в основному шматків коду), щоб потім не гуглити їх заново.

Досить поширена задача:
Підрахувати кількість входжень кожного елементу (наприклад, IP-адрес з лог-файлу Apache). Очевидно, для цього потрібно використовувати Hash, щоб зберігати число лічильників для ключів. Можна просто виконувати операцію += 1 для членів хешу.

Варіант 1
> languages = {}
 => {}
> languages['ukrainian'] = 0
 => 0 
> languages['ukrainian'] += 1
 => 1 
> languages
 => {"ukrainian"=>1}
> languages['russian'] += 1
NoMethodError: undefined method `+' for nil:NilClass
В Ruby, ви повинні ініціалізувати змінні. Іншими словами, ви не можете використовувати зміст змінної, якщо її не існує.

Друге, що потрібно зробити - автоматично створювати пару ключ-значення, якщо ключа не існує.
> languages['russian'] = languages.fetch('russian', 0) + 1
 => 1 
> languages
 => {"ukrainian"=>1, "russian"=>1}

Всю магію тут виконує метод Hash#fetch(key [, default] ), який повертає значення з хешу для ключа key, а якщо його не знайдено, повертає значення default.

Варіант 2
Існує простіший спосіб. Встановити значення для ключа, якого не існує в хеші (по замовчуванняю це nil).
> languages = {}
 => {} 
> languages.default
 => nil 
> languages.default = 0
 => 0 
> languages['ukrainian'] += 1
 => 1 
> languages['russian'] += 1
 => 1 
> languages
 => {"ukrainian"=>1, "russian"=>1}
> languages['english']
 => 0
Зверніть увагу, якщо запитати ключ, якого не існує в хеші, то поверне значення по замовчуванню. Тут потрібно бути обережним.

Використовуйте в залежності від ваших задач. І не забувайте читати офіційну документацію.
Успіхів!

2 коментарі:

Анонім сказав...

http://ruby-doc.org/ruby-1.9/classes/Hash.html#M000369

Hash.new(obj) => aHash
If obj is specified, this single object will be used for all default values.

irb(main):001:0> h=Hash.new(0)
=> {}
irb(main):005:0> h['b']+=1
=> 1
irb(main):006:0> h['c']
=> 0

Roman V. Babenko сказав...

>> [2, 4, 5, 4, 4, 5].group_by {|e| e }.inject({}) {|a, b| a[b.first] = b.last.size; a }
=> {2=>1, 4=>3, 5=>2}