Ruby Language
Хэш
Поиск…
Вступление
Хеш - это словарь-подобный набор уникальных ключей и их значений. Также называемые ассоциативные массивы, они похожи на массивы, но где Array использует целые числа в качестве своего индекса, Hash позволяет использовать любой тип объекта. Вы извлекаете или создаете новую запись в Hash, ссылаясь на ее ключ.
Синтаксис
{first_name: "Noel", second_name: "Edmonds"}
{: first_name => "Noel",: second_name => "Edmonds"}
{"Имя" => "Ноэль", "Второе имя" => "Эдмондс"}
{first_key => first_value, second_key => second_value}
замечания
Хеши в Ruby сопоставляют ключи с значениями, используя хеш-таблицу.
Любой хешируемый объект может использоваться как ключ. Тем не менее, очень часто используется Symbol
поскольку он обычно более эффективен в нескольких версиях Ruby из-за уменьшения распределения объектов.
{ key1: "foo", key2: "baz" }
Создание хэша
Хэш в Ruby - это объект, который реализует хеш-таблицу , сопоставляя ключи со значениями. Ruby поддерживает определенный литерал синтаксиса для определения хэшей с помощью {}
:
my_hash = {} # an empty hash
grades = { 'Mark' => 15, 'Jimmy' => 10, 'Jack' => 10 }
Хэш также может быть создан с использованием стандартного new
метода:
my_hash = Hash.new # any empty hash
my_hash = {} # any empty hash
Хэши могут иметь значения любого типа, включая сложные типы, такие как массивы, объекты и другие хеши:
mapping = { 'Mark' => 15, 'Jimmy' => [3,4], 'Nika' => {'a' => 3, 'b' => 5} }
mapping['Mark'] # => 15
mapping['Jimmy'] # => [3, 4]
mapping['Nika'] # => {"a"=>3, "b"=>5}
Также ключи могут быть любого типа, включая сложные:
mapping = { 'Mark' => 15, 5 => 10, [1, 2] => 9 }
mapping['Mark'] # => 15
mapping[[1, 2]] # => 9
Символы обычно используются как хеш-ключи, а Ruby 1.9 вводит новый синтаксис, чтобы сократить этот процесс. Следующие хэши эквивалентны:
# Valid on all Ruby versions
grades = { :Mark => 15, :Jimmy => 10, :Jack => 10 }
# Valid in Ruby version 1.9+
grades = { Mark: 15, Jimmy: 10, Jack: 10 }
Следующий хеш (действительный во всех версиях Ruby) отличается , поскольку все ключи являются строками:
grades = { "Mark" => 15, "Jimmy" => 10, "Jack" => 10 }
Хотя обе версии синтаксиса могут быть смешаны, следующее не рекомендуется.
mapping = { :length => 45, width: 10 }
С Ruby 2.2+ существует альтернативный синтаксис для создания хэша с символьными клавишами (наиболее полезно, если символ содержит пробелы):
grades = { "Jimmy Choo": 10, :"Jack Sparrow": 10 }
# => { :"Jimmy Choo" => 10, :"Jack Sparrow" => 10}
Доступ к значениям
Отдельные значения хэша считываются и записываются с использованием методов []
и []=
:
my_hash = { length: 4, width: 5 }
my_hash[:length] #=> => 4
my_hash[:height] = 9
my_hash #=> {:length => 4, :width => 5, :height => 9 }
По умолчанию доступ к ключу, который не был добавлен в хеш, возвращает nil
, что означает, что всегда можно попытаться найти значение ключа:
my_hash = {}
my_hash[:age] # => nil
Хэши также могут содержать ключи в строках. Если вы попытаетесь получить к ним доступ обычно, он просто вернет nil
, вместо этого вы получите к ним доступ по строковым ключам:
my_hash = { "name" => "user" }
my_hash[:name] # => nil
my_hash["name"] # => user
В ситуациях, когда ключи ожидаются или должны существовать, хеши имеют метод fetch
который вызывает исключение при доступе к ключу, который не существует:
my_hash = {}
my_hash.fetch(:age) #=> KeyError: key not found: :age
fetch
принимает значение по умолчанию в качестве второго аргумента, которое возвращается, если ключ еще не был установлен:
my_hash = {}
my_hash.fetch(:age, 45) #=> => 45
fetch
также может принимать блок, который возвращается, если ключ еще не был установлен:
my_hash = {}
my_hash.fetch(:age) { 21 } #=> 21
my_hash.fetch(:age) do |k|
puts "Could not find #{k}"
end
#=> Could not find age
Хэши также поддерживают метод store
как псевдоним для []=
:
my_hash = {}
my_hash.store(:age, 45)
my_hash #=> { :age => 45 }
Вы также можете получить все значения хэша с помощью метода values
:
my_hash = { length: 4, width: 5 }
my_hash.values #=> [4, 5]
Примечание. Это только для Ruby 2.3+ #dig
удобно для вложенных Hash
s. Извлекает вложенное значение, заданное последовательностью объектов idx, вызывая коп на каждом шаге, возвращая нуль, если какой-либо промежуточный шаг равен нулю.
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) # => 1
h.dig(:foo, :zot, :xyz) # => nil
g = { foo: [10, 11, 12] }
g.dig(:foo, 1) # => 11
Установка значений по умолчанию
По умолчанию попытка поиска значения для ключа, который не существует, будет возвращать nil
. Вы можете указать другое возвращаемое значение (или действие, которое нужно предпринять), когда хеш обращается с несуществующим ключом. Хотя это называется «значением по умолчанию», это не должно быть ни одного значения; он может, например, быть вычисленным значением, таким как длина ключа.
Значение хэша по умолчанию может быть передано его конструктору:
h = Hash.new(0)
h[:hi] = 1
puts h[:hi] # => 1
puts h[:bye] # => 0 returns default value instead of nil
Значение по умолчанию также может быть указано на уже сконструированном Hash:
my_hash = { human: 2, animal: 1 }
my_hash.default = 0
my_hash[:plant] # => 0
Важно отметить, что значение по умолчанию не копируется каждый раз при обращении к новому ключу, что может привести к неожиданным результатам, когда значение по умолчанию является ссылочным типом:
# Use an empty array as the default value
authors = Hash.new([])
# Append a book title
authors[:homer] << 'The Odyssey'
# All new keys map to a reference to the same array:
authors[:plato] # => ['The Odyssey']
Чтобы обойти эту проблему, конструктор Hash принимает блок, который выполняется каждый раз, когда к нему обращается новый ключ, а возвращаемое значение используется как значение по умолчанию:
authors = Hash.new { [] }
# Note that we're using += instead of <<, see below
authors[:homer] += ['The Odyssey']
authors[:plato] # => []
authors # => {:homer=>["The Odyssey"]}
Обратите внимание, что выше мы должны были использовать + = вместо <<, потому что значение по умолчанию автоматически не присваивается хэшу; использование << добавило бы в массив, но авторы [: Гомер] остались бы неопределенными:
authors[:homer] << 'The Odyssey' # ['The Odyssey']
authors[:homer] # => []
authors # => {}
Чтобы иметь возможность назначать значения по умолчанию для доступа, а также вычислять более сложные значения по умолчанию, блок по умолчанию передается как хэш, так и ключ:
authors = Hash.new { |hash, key| hash[key] = [] }
authors[:homer] << 'The Odyssey'
authors[:plato] # => []
authors # => {:homer=>["The Odyssey"], :plato=>[]}
Вы также можете использовать блок по умолчанию для принятия действия и / или возврата значения, зависящего от ключа (или некоторых других данных):
chars = Hash.new { |hash,key| key.length }
chars[:test] # => 4
Вы даже можете создавать более сложные хэши:
page_views = Hash.new { |hash, key| hash[key] = { count: 0, url: key } }
page_views["http://example.com"][:count] += 1
page_views # => {"http://example.com"=>{:count=>1, :url=>"http://example.com"}}
Чтобы установить значение по умолчанию для Proc для уже существующего хеша, используйте default_proc=
:
authors = {}
authors.default_proc = proc { [] }
authors[:homer] += ['The Odyssey']
authors[:plato] # => []
authors # {:homer=>["The Odyssey"]}
Автоматическое создание Deep Hash
Хэш имеет значение по умолчанию для запрошенных ключей, но не существует (ноль):
a = {}
p a[ :b ] # => nil
При создании нового хэша можно указать значение по умолчанию:
b = Hash.new 'puppy'
p b[ :b ] # => 'puppy'
Hash.new также принимает блок, который позволяет автоматически создавать вложенные хэши, такие как поведение автовивитации Perl или mkdir -p
:
# h is the hash you're creating, and k the key.
#
hash = Hash.new { |h, k| h[k] = Hash.new &h.default_proc }
hash[ :a ][ :b ][ :c ] = 3
p hash # => { a: { b: { c: 3 } } }
Изменение ключей и значений
Вы можете создать новый хеш с измененными ключами или значениями, действительно, вы также можете добавлять или удалять ключи, используя инъекцию (AKA, сокращение ). Например, для создания хеша с строковыми ключами и значениями верхнего регистра:
fruit = { name: 'apple', color: 'green', shape: 'round' }
# => {:name=>"apple", :color=>"green", :shape=>"round"}
new_fruit = fruit.inject({}) { |memo, (k,v)| memo[k.to_s] = v.upcase; memo }
# => new_fruit is {"name"=>"APPLE", "color"=>"GREEN", "shape"=>"ROUND"}
Хэш является перечислимым, по сути, набором пар ключ / значение. Поэтому есть такие методы, как each
, map
и inject
.
Для каждой пары «ключ / значение» в хэше данный блок оценивается, значение memo в первом прогоне - это начальное значение, переданное для inject
, в нашем случае пустой хэш, {}
. Значение memo
для последующих оценок - это возвращаемое значение предыдущей оценки блоков, поэтому мы модифицируем memo
, установив ключ со значением и затем возвращаем memo
в конце. Возвращаемое значение оценки конечных блоков - это возвращаемое значение inject
, в нашем случае - memo
.
Чтобы избежать необходимости предоставлять конечное значение, вы можете использовать each_with_object :
new_fruit = fruit.each_with_object({}) { |(k,v), memo| memo[k.to_s] = v.upcase }
Или даже карту :
new_fruit = Hash[fruit.map{ |k,v| [k.to_s, v.upcase] }]
(См. Этот ответ для более подробной информации, в том числе о том, как манипулировать хэшами на месте.)
Итерация над хешей
Hash
включает в себя модуль Enumerable
, который предоставляет несколько методов итерации, таких как: Enumerable#each
, Enumerable#each_pair
, Enumerable#each_key
и Enumerable#each_value
.
.each
и .each_pair
перебирают по каждой паре ключ-значение:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each do |key, value|
puts "#{key} = #{value}"
end
# => first_name = John
# last_name = Doe
.each_key
выполняет .each_key
по клавишам:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_key do |key|
puts key
end
# => first_name
# last_name
.each_value
выполняет .each_value
по значениям:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_value do |value|
puts value
end
# => John
# Doe
.each_with_index
выполняет .each_with_index
по элементам и предоставляет индекс итерации:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_with_index do |(key, value), index|
puts "index: #{index} | key: #{key} | value: #{value}"
end
# => index: 0 | key: first_name | value: John
# index: 1 | key: last_name | value: Doe
Преобразование в и из массивов
Хеши могут быть свободно преобразованы в массивы и из них. Преобразование хэша пар ключ / значение в массив приведет к созданию массива, содержащего вложенные массивы для пары:
{ :a => 1, :b => 2 }.to_a # => [[:a, 1], [:b, 2]]
В обратном направлении хэш может быть создан из массива того же формата:
[[:x, 3], [:y, 4]].to_h # => { :x => 3, :y => 4 }
Аналогично, хэш может быть инициализирован с использованием Hash[]
и списка переменных ключей и значений:
Hash[:a, 1, :b, 2] # => { :a => 1, :b => 2 }
Или из массива массивов с двумя значениями каждый:
Hash[ [[:x, 3], [:y, 4]] ] # => { :x => 3, :y => 4 }
Хеши могут быть преобразованы обратно в массив переменных ключей и значений с помощью flatten()
:
{ :a => 1, :b => 2 }.flatten # => [:a, 1, :b, 2]
Легкое преобразование в массив и из массива позволяет Hash
хорошо работать со многими методами Enumerable
такими как collect
и zip
:
Hash[('a'..'z').collect{ |c| [c, c.upcase] }] # => { 'a' => 'A', 'b' => 'B', ... }
people = ['Alice', 'Bob', 'Eve']
height = [5.7, 6.0, 4.9]
Hash[people.zip(height)] # => { 'Alice' => 5.7, 'Bob' => '6.0', 'Eve' => 4.9 }
Получение всех ключей или значений хэша
{foo: 'bar', biz: 'baz'}.keys # => [:foo, :biz]
{foo: 'bar', biz: 'baz'}.values # => ["bar", "baz"]
{foo: 'bar', biz: 'baz'}.to_a # => [[:foo, "bar"], [:biz, "baz"]]
{foo: 'bar', biz: 'baz'}.each #<Enumerator: {:foo=>"bar", :biz=>"baz"}:each>
Переопределение хэш-функции
Рубиновые хеши используют методы hash
и eql?
для выполнения операции хеширования и назначения объектов, хранящихся в хэше, внутренним хэш-ячейкам. Реализация hash
по умолчанию в Ruby - это хэш-функция murmur по всем полям элемента хэшированного объекта . Чтобы переопределить это поведение, можно переопределить hash
и eql?
методы.
Как и в случае с другими реализациями хэша, два объекта a и b будут хэшироваться в том же ведро, если a.hash == b.hash
и будут считаться идентичными, если a.eql?(b)
. Таким образом, при повторной реализации hash
и eql?
следует позаботиться о том, чтобы, если a
и b
равны по eql?
они должны возвращать одно и то же значение hash
функции. В противном случае это может привести к дублированию записей в хэше. И наоборот, плохой выбор в реализации hash
может привести к тому, что многие объекты будут совместно использовать один и тот же хэш-ведро, эффективно разрушая время поиска O (1) и вызывая O (n) для вызова eql?
на всех объектах.
В приведенном ниже примере только экземпляр класса A
хранится в виде ключа, поскольку он был добавлен первым:
class A
def initialize(hash_value)
@hash_value = hash_value
end
def hash
@hash_value # Return the value given externally
end
def eql?(b)
self.hash == b.hash
end
end
class B < A
end
a = A.new(1)
b = B.new(1)
h = {}
h[a] = 1
h[b] = 2
raise "error" unless h.size == 1
raise "error" unless h.include? b
raise "error" unless h.include? a
Фильтрация хэшей
select
возвращает новый hash
с парами ключ-значение, для которых блок оценивается как true
.
{ :a => 1, :b => 2, :c => 3 }.select { |k, v| k != :a && v.even? } # => { :b => 2 }
Когда вам не понадобится ключ или значение в блоке фильтра, соглашение должно использовать _
в этом месте:
{ :a => 1, :b => 2, :c => 3 }.select { |_, v| v.even? } # => { :b => 2 }
{ :a => 1, :b => 2, :c => 3 }.select { |k, _| k == :c } # => { :c => 3 }
reject
возвращает новый hash
с парами ключ-значение, для которых блок оценивается как false
:
{ :a => 1, :b => 2, :c => 3 }.reject { |_, v| v.even? } # => { :a => 1, :c => 3 }
{ :a => 1, :b => 2, :c => 3 }.reject { |k, _| k == :b } # => { :a => 1, :c => 3 }
Установить операции с хэшами
Пересечение хешей
Чтобы получить пересечение двух хэшей, верните общие ключи, значения которых равны:
hash1 = { :a => 1, :b => 2 } hash2 = { :b => 2, :c => 3 } hash1.select { |k, v| (hash2.include?(k) && hash2[k] == v) } # => { :b => 2 }
Союз (слияние) хешей:
ключи в хэше уникальны, если в обоих хэшах, которые должны быть объединены, возникает ключ, тот из хэша, который вызывается
merge
перезаписывается:hash1 = { :a => 1, :b => 2 } hash2 = { :b => 4, :c => 3 } hash1.merge(hash2) # => { :a => 1, :b => 4, :c => 3 } hash2.merge(hash1) # => { :b => 2, :c => 3, :a => 1 }