вторник, 1 ноября 2011 г.

Ruby: Hash — когда ключ не найден

Есть в стандартном классе Hash такая замечательная возможность — определить значение по-умолчанию, которое будет выдаваться вместо nil при обращении по неизвестному ключу. Например:

h = Hash.new("")
p h[:alpha]

выдаст именно пустую строку, а не пустое значение. Это само по себе может быть весьма полезным, но возможности Hash.new() этим не ограничиваются.

Гораздо интересней использование Hash.new() с блоком, в котором можно сформировать произвольное значение. Простой (и, конечно, надуманный) пример — пусть мы хотим хранить что-то типа адресной книги так, чтобы по обращению с ключом типа String получать записанные опять же в хэш некие свойства (адрес, телефон и т.д.).

Если это делать «в лоб», то установка свойства будет выглядеть примерно так:

if book["Vasya Pupkin"] == nil
  book["Vasya Pupkin"] = {}
end
book["Vasya Pupkin"][:email] = "vasya@pupkin.name"

Нет, мы, конечно, можем воспользоваться «синтаксическим сахаром» и превратить первые три строки в одну...

book["Vasya Pupkin"] ||= {}
book["Vasya Pupkin"][:email] = "vasya@pupkin.name"

Правда, выиграв немного в краткости, проигрываем (и сильно) в понятности... Получение свойства тоже будет не ахти:

if book["Vasya Pupkin"]
  book["Vasya Pupkin"][:email]
else
  nil
end

Ну, или какая-нибудь прелестная конструкция типа:

book["Vasya Pupkin"] && book["Vasya Pupkin"][:email]

И что самое главное — эта вот проверка на то, что для ключа задано правильное значение, будет постоянно забываться и выпадать в виде ошибки в самых непредвиденных местах.

Напрашивающийся вариант с инициализацией book = Hash.new({}) дает (в Ruby 1.8.7) вообще неожиданный результат:

book = Hash.new({})

book["Vasya Pupkin"][:email] = "vasya@pupkin.name"
book["Masha Ivanova"][:phone] = "212-85-06"

p book
p book["Kolya Sidorov"]

Выдает следующее:

{}
{:phone=>"212-85-06", :email=>"vasya@pupkin.name"}

И это, как ни странно, логично — просто значением по-умолчанию становится не литерал {}, а объект, изначально им инициализированный, но в дальнейшем претерпевающий изменения.

И что делать? А вот что:

book = Hash.new do |hash, key|
  hash[key] = {}
end

Очевидно, что внутри блока мы можем опять же воспользоваться вместо литерала вызовом Hash.new() с блоком и, таким образом, получить хэш произвольной вложенности (ну, или более хитрую структуру, если нужно).

Комментариев нет:

Отправить комментарий