Хеш и символы


Ассоциативный массив, или хеш — это очень удобная структура данных, которая предоставляет с собой как и массив парную структуру, но ключ не упорядочен и может принимать любое значение.
Все хеши имеют тип Hash, то есть являются экземплярами класса Hash. ниже приведены способы объявления хэша:
a = {} #=> {}
a = Hash.new #=> {}
a = Hash[] #=> {}

Основными отличиями хеша от массива является то, что в хэше ключи должны быть присвоены элементу явно, кроме того, ключом может быть любой объект, а не только целое число, как то есть в массивах. Пример хеша:
hash = {'hello' => 'goodbye', 1 => "Hey!", [1]=>[1,2,3,4,5]} #=> {"hello"=>"goodbye", 1=>"Hey!", [1]=>[1, 2, 3, 4, 5]}

hash['hello'] #=> "goodbye"
hash[1] #=> "Hey!"
hash[[1]] #=> [1, 2, 3, 4, 5]
hash[[1]][1] #=> 2

Как видите, хеши позволяют также хранить любые типы данных и могут быть произвольного размера, который ограничивается только оперативной памятью компьютера, кроме того, ключи также могут иметь различный тип. Работа с хешами сильно похожа на работу с массивами, поэтому мы не будем рассматривать их так же подробно.
Вам следует помнить, что хотя массивы и позволяют хранить элементы различных типов, это не является самым хорошим стилем. Вам следует использовать массивы для хранения более-менее одинаковых элементов или вложенных структур, которые в свою очередь так же могут быть массивами или хешами. В идеале ваши структуры должны иметь следующий вид:
fruits = []
fruits << {"name"=>"banana", "cost"=>10} << {"name"=>"apple", "cost"=>7}
#=> [{"name"=>"banana", "cost"=>10}, {"name"=>"apple", "cost"=>7}]

Только что мы получили массив однородных сущностей — фруктов, именно для хранения однородных сущностей наиболее всего подходят массивы, поскольку не обладают «говорящими» индексами (ключами) элементов.

Хэши — это те же массивы, но обладающие ключами, которые описывают информацию, которая за ними скрывается в отличие от индексов в массивах, которые просто указывают порядок следования элементов.
Прежде, чем мы перейдем к использованию хешей, мы рассмотрим еще один полезный тип данных в Ruby — символы.

Символ

Символы — это специфичные строки, которые в программе id указывается один раз и на всегда. Ниже приведен простой пример символа:
:symbol.class #=> Symbol

Как видите, символ — это просто строка, без кавычек, которая начинается с символа двоеточия и которая имеет тип Symbol.
Причина, почему были введены символы, когда есть строки заключается в том, что хранение символов в памяти компьютера происходит так же, как и хранение чисел, что позволяет экономить потребление операционной памяти и во время выполнения программы, примеры:
'hello world'.object_id #=> 70115500
'hello world'.object_id #=> 70106870
'hello world'.object_id #=> 70104250
'hello world'.object_id #=> 70101670
:hello_world.object_id #=> 186168
:hello_world.object_id #=> 186168
:hello_world.object_id #=> 186168
5.object_id #=> 11
5.object_id #=> 11
5.object_id #=> 11

Метод #object_id возвращает уникальный идентификатор объекта — настоящее имя объекта, по которому к нему можно обратиться в оперативной памяти. Как видите, у одинаковых строк ID разный, а значит и каждая строка, хоть и имеет одинаковое содержимое представлена своим собственным объектом, поэтому, для хранения одной и той же информации в строке создается целых четыре объекта.

У чисел и символов все иначе, одинаковые числа и символы не плодят новых объектов и это видно из того, что ID объекта для одинаковых чисел и символов одинаков. Такое поведение символов делает выгодным их использование в качестве ключей для хешей. Почему? — Вы только представьте хеш состоящий из несколько миллионов пар ключ-значение, где ключом является строка.

В результате создания такого хеша нам понадобится, во первых, создать несколько миллионов объектов только для хранения ключей, да еще и хранить их в оперативной памяти, как результат, оперативной памяти может не хватить, а генерация огромного количество объектов может занять большое количество времени, ровно, как и уничтожение объектов после того, как необходимость в их использовании отпадает. В таком случае нам следует использовать символы и вместо миллионов объектов мы можем хранить 1 — 10 или N объектов в зависимости от структуры хеша, что несоизмеримо меньше с миллионами объектов при использовании строк.
Использование символов более удобное, поскольку не нужно использовать надоедливые кавычки. Вот пример как использовать хеш

# Первый вариант использования
hash = {:cost => 150, :weight => 200, :color => "green"}


# Второй вариант
hash = {cost: 150, weight: 200, height: "green"}

Как видите, второй синтаксис более удобен, поэтому я рекомендую использовать именно его.

В ruby 2.0 появилась поддержка литерала "%i", предназначенного для упрощенного создания массива символов
keys = %i[foo bar baz] #=>[:foo, :bar, :baz]
вместо
keys = [:foo, :bar, :baz] #=> [:foo, :bar, :baz]

#to_s - данный метод используется для преобразования объекта в строку, если быть совсем точным, то объект преобразовываться в другой тип не может, просто создается другой, аналогичный объект — строкового типа, пример:
:symbol.to_s #=> "symbol"

#to_sym — сей метод предназначен для получения соответствующего строке символа, пример:
"string".to_sym #=> :string


Еще один малоизвестный способ преобразовать строку в символ — просто поставить перед строкой двоеточие, данный способ, вполне может заменить метод to_s в тех случаях, когда используется непосредственно для строки, а не для переменной ссылающейся на нее, примеры:
symbol = "string".to_sym #=> :string
symbol.object_id #=> 54088

symbol2 = :"string" #=> :string
symbol2.object_id # => 54088



Использование хешей

Хеши очень любят символы, об этом я уже упоминал выше, когда писал о том, зачем были придуманы эти самые символы. Поскольку, хеши имеют достаточно много общих методом в массивами, я не буду зацикливать внимание на подробном их разборе, просто приведу примеры работы с хэшами, ну и остановлюсь, на некоторых важных и уникальных методах.
user = {name:"Vasya", last_name: "Petrov", age: 20}
user[:name] #=> "Vasya"
user[:last_name] #=> "Petrov"
user["last_name".to_sym] #=> "Petrov"


Синтаксис создания хеша с Hash.new позволяет принимать блок кода, который выполняется при обращении к не существующему ключу, создает его и соответствующее ему значение по правилам определенным в блоке кода, пример:
hash = Hash.new{|h,k| h[k] = k} #=> {}
hash[:a] #=> :a
hash #=> {:a=>:a}

Другими словами, в такой способ можно создать значение по умолчанию.
Синтаксис Hash[] позволяет создавать хеш из предоставляемого массива. Такой синтаксис создания хеша, как бы просит у переданного массива рассчитаться на 1й — 2й, где 1й — ключ, 2й — значение, пример:
hash = Hash[1,2,3,4,5,6] #=> {1=>2, 3=>4, 5=>6}
hash = Hash[:a,1,:b,2,:array, [1,2,3,4]] #=> {:a=>1, :b=>2, :array=>[1, 2, 3, 4]}
hash = Hash[[[:a,:b,:c,:d],[1,2,3,4]].transpose] #=> {:a=>1, :b=>2, :c=>3, :d=>4}


#default - данный метод позволяет установить хешу значение по умолчанию. В массивах значение по умолчанию всегда nil, а в хэшах его можно очень просто установить, пример:
h=Hash.new #=> {}
h[:a] #=> nil
h.default = 'text'
h[:a] #=> "text"
h[:b] #=> "text"

#invert — данный метод позволяет преобразовать структуру хэша так, что ключи и значения меняются местами, пример:
h = {a: 100, b: 200, c: 300}
h.invert #=> {100=>:a, 200=>:b, 300=>:c}


Массив -> Хэш -> Массив
Для преобразования массива в хеш достаточно в ruby 2.0 добавили
#to_h - метод преобразовывает массив в хеш
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}


#to_a - метод для преобразования хеша в массив, пример
hash #=> {1=>2, 3=>4}
hash.to_a #=> [[1, 2], [3, 4]]

Чтобы конвертировать хэш в массив с такой структурой, как тот, из которого создавался хеш, необходимо добавить в цепочку методов знакомый нам #flatten:
hash.to_a.flatten #=> [1, 2, 3, 4]