Поиск…


Вступление

Хеш - это словарь-подобный набор уникальных ключей и их значений. Также называемые ассоциативные массивы, они похожи на массивы, но где 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 }

Или даже карту :

1,8
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 }
    


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow