Ruby Language
Hashes
Szukaj…
Wprowadzenie
Hash to słownikopodobna kolekcja unikalnych kluczy i ich wartości. Znane również jako tablice asocjacyjne, są podobne do tablic, ale tam, gdzie tablica używa liczb całkowitych jako indeksu, Hash pozwala na użycie dowolnego typu obiektu. Pobierasz lub tworzysz nowy wpis w haszu, odwołując się do jego klucza.
Składnia
{first_name: „Noel”, second_name: „Edmonds”}
{: first_name => „Noel”,: second_name => „Edmonds”}
{„First Name” => „Noel”, „Second Name” => „Edmonds”}
{first_key => first_value, second_key => second_value}
Uwagi
Skróty w kluczach mapy Ruby do wartości przy użyciu tabeli skrótów.
Dowolny obiekt mieszalny może być używany jako klucze. Jednak bardzo często używa się Symbol
ponieważ jest on ogólnie bardziej wydajny w kilku wersjach Ruby, ze względu na ograniczoną alokację obiektów.
{ key1: "foo", key2: "baz" }
Tworzenie skrótu
Hash w Ruby to obiekt, który implementuje tablicę skrótów , mapując klucze na wartości. Ruby obsługuje określoną dosłowną składnię do definiowania skrótów za pomocą {}
:
my_hash = {} # an empty hash
grades = { 'Mark' => 15, 'Jimmy' => 10, 'Jack' => 10 }
Skrót można również utworzyć przy użyciu standardowej new
metody:
my_hash = Hash.new # any empty hash
my_hash = {} # any empty hash
Skróty mogą mieć wartości dowolnego typu, w tym typy złożone, takie jak tablice, obiekty i inne skróty:
mapping = { 'Mark' => 15, 'Jimmy' => [3,4], 'Nika' => {'a' => 3, 'b' => 5} }
mapping['Mark'] # => 15
mapping['Jimmy'] # => [3, 4]
mapping['Nika'] # => {"a"=>3, "b"=>5}
Klucze mogą być dowolnego typu, w tym złożone:
mapping = { 'Mark' => 15, 5 => 10, [1, 2] => 9 }
mapping['Mark'] # => 15
mapping[[1, 2]] # => 9
Symbole są powszechnie używane jako klucze skrótu, a Ruby 1.9 wprowadził nową składnię specjalnie w celu skrócenia tego procesu. Następujące skróty są równoważne:
# 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 }
Poniższy skrót (poprawny we wszystkich wersjach Ruby) jest inny , ponieważ wszystkie klucze są ciągami:
grades = { "Mark" => 15, "Jimmy" => 10, "Jack" => 10 }
Chociaż obie wersje składni można mieszać, odradza się poniższe.
mapping = { :length => 45, width: 10 }
W Ruby 2.2+ istnieje alternatywna składnia do tworzenia skrótu za pomocą klawiszy symboli (najbardziej przydatne, jeśli symbol zawiera spacje):
grades = { "Jimmy Choo": 10, :"Jack Sparrow": 10 }
# => { :"Jimmy Choo" => 10, :"Jack Sparrow" => 10}
Dostęp do wartości
Poszczególne wartości skrótu są odczytywane i zapisywane przy użyciu metod []
i []=
:
my_hash = { length: 4, width: 5 }
my_hash[:length] #=> => 4
my_hash[:height] = 9
my_hash #=> {:length => 4, :width => 5, :height => 9 }
Domyślnie dostęp do klucza, który nie został dodany do skrótu, wynosi nil
, co oznacza, że zawsze można bezpiecznie sprawdzić wartość klucza:
my_hash = {}
my_hash[:age] # => nil
Hashe mogą także zawierać klucze w ciągach znaków. Jeśli spróbujesz uzyskać do nich normalny dostęp, po prostu zwróci nil
, zamiast tego uzyskujesz do nich dostęp za pomocą ich kluczy łańcuchowych:
my_hash = { "name" => "user" }
my_hash[:name] # => nil
my_hash["name"] # => user
W sytuacjach, w których oczekuje się lub wymagane są klucze, skróty mają metodę fetch
, która zgłosi wyjątek podczas uzyskiwania dostępu do klucza, który nie istnieje:
my_hash = {}
my_hash.fetch(:age) #=> KeyError: key not found: :age
fetch
przyjmuje wartość domyślną jako swój drugi argument, który jest zwracany, jeśli klucz nie został wcześniej ustawiony:
my_hash = {}
my_hash.fetch(:age, 45) #=> => 45
fetch
może również zaakceptować blok, który jest zwracany, jeśli klucz nie został wcześniej ustawiony:
my_hash = {}
my_hash.fetch(:age) { 21 } #=> 21
my_hash.fetch(:age) do |k|
puts "Could not find #{k}"
end
#=> Could not find age
Hashe obsługują również metodę store
jako alias dla []=
:
my_hash = {}
my_hash.store(:age, 45)
my_hash #=> { :age => 45 }
Możesz także uzyskać wszystkie wartości skrótu za pomocą metody values
:
my_hash = { length: 4, width: 5 }
my_hash.values #=> [4, 5]
Uwaga: To jest tylko dla Ruby 2.3+ #dig
jest przydatny dla zagnieżdżonych Hash
s. Wyodrębnia zagnieżdżoną wartość określoną przez sekwencję obiektów idx, wywołując dig na każdym kroku, zwracając zero, jeśli jakikolwiek krok pośredni jest zerowy.
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
Ustawianie wartości domyślnych
Domyślnie próba wyszukania wartości nieistniejącego klucza zwróci nil
. Opcjonalnie można określić inną wartość, która ma zostać zwrócona (lub akcję, którą należy podjąć), gdy dostęp do skrótu uzyskuje się za pomocą nieistniejącego klucza. Chociaż jest to określane jako „wartość domyślna”, nie musi to być pojedyncza wartość; może to być na przykład wartość obliczona, taka jak długość klucza.
Domyślną wartość skrótu można przekazać do jego konstruktora:
h = Hash.new(0)
h[:hi] = 1
puts h[:hi] # => 1
puts h[:bye] # => 0 returns default value instead of nil
Wartość domyślną można również określić w już zbudowanym skrócie:
my_hash = { human: 2, animal: 1 }
my_hash.default = 0
my_hash[:plant] # => 0
Należy zauważyć, że wartość domyślna nie jest kopiowana za każdym razem, gdy uzyskuje się dostęp do nowego klucza, co może prowadzić do zaskakujących wyników, gdy wartość domyślna jest typem odniesienia:
# 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']
Aby obejść ten problem, konstruktor skrótu akceptuje blok, który jest wykonywany za każdym razem, gdy uzyskiwany jest dostęp do nowego klucza, a zwrócona wartość jest używana jako domyślna:
authors = Hash.new { [] }
# Note that we're using += instead of <<, see below
authors[:homer] += ['The Odyssey']
authors[:plato] # => []
authors # => {:homer=>["The Odyssey"]}
Zauważ, że powyżej musieliśmy użyć + = zamiast <<, ponieważ domyślna wartość nie jest automatycznie przypisywana do skrótu; użycie << dodałoby do tablicy, ale autorzy [: homer] pozostaliby niezdefiniowani:
authors[:homer] << 'The Odyssey' # ['The Odyssey']
authors[:homer] # => []
authors # => {}
Aby móc przypisać wartości domyślne podczas dostępu, a także obliczyć bardziej wyrafinowane wartości domyślne, blok domyślny jest przekazywany zarówno hash, jak i klucz:
authors = Hash.new { |hash, key| hash[key] = [] }
authors[:homer] << 'The Odyssey'
authors[:plato] # => []
authors # => {:homer=>["The Odyssey"], :plato=>[]}
Możesz także użyć domyślnego bloku, aby wykonać akcję i / lub zwrócić wartość zależną od klucza (lub niektórych innych danych):
chars = Hash.new { |hash,key| key.length }
chars[:test] # => 4
Możesz nawet tworzyć bardziej złożone skróty:
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"}}
Aby ustawić domyślną wartość Proc na już istniejącym haszu, użyj default_proc=
:
authors = {}
authors.default_proc = proc { [] }
authors[:homer] += ['The Odyssey']
authors[:plato] # => []
authors # {:homer=>["The Odyssey"]}
Automatyczne tworzenie Deep Hash
Skrót ma domyślną wartość dla kluczy, które są wymagane, ale nie istnieją (zero):
a = {}
p a[ :b ] # => nil
Podczas tworzenia nowego skrótu można określić domyślne:
b = Hash.new 'puppy'
p b[ :b ] # => 'puppy'
Hash.new pobiera również blok, który umożliwia automatyczne tworzenie zagnieżdżonych skrótów, takich jak zachowanie autoweryfikacji Perla lub 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 } } }
Modyfikowanie kluczy i wartości
Możesz utworzyć nowy skrót ze zmodyfikowanymi kluczami lub wartościami, w rzeczywistości możesz także dodawać lub usuwać klucze za pomocą wstrzykiwania (AKA, zmniejsz ). Na przykład, aby utworzyć skrót z kluczami strunowymi i dużymi literami:
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"}
Hash jest policzalną, w zasadzie zbiorem par klucz / wartość. Dlatego ma takie metody, jak each
, map
i inject
.
Dla każdej pary klucz / wartość w haszu analizowany jest dany blok, wartość notatki przy pierwszym uruchomieniu jest wartością początkową przekazywaną do inject
, w naszym przypadku pustym hashem {}
. Wartość memo
dla kolejnych ocen jest wartością zwracaną z oceny poprzednich bloków, dlatego modyfikujemy memo
ustawiając klucz z wartością, a następnie zwracamy memo
na końcu. Zwracana wartość końcowego bloku jest wartością zwracaną przez inject
, w naszym przypadku memo
.
Aby uniknąć konieczności podawania końcowej wartości, możesz zamiast tego użyć each_with_object :
new_fruit = fruit.each_with_object({}) { |(k,v), memo| memo[k.to_s] = v.upcase }
Lub nawet mapa :
new_fruit = Hash[fruit.map{ |k,v| [k.to_s, v.upcase] }]
(Zobacz tę odpowiedź, aby uzyskać więcej informacji, w tym jak manipulować skrótami w miejscu).
Iterating Over Hash
Hash
zawiera moduł Enumerable
, który zapewnia kilka metod iteracji, takich jak: Enumerable#each
, Enumerable#each_pair
, Enumerable#each_key
i Enumerable#each_value
.
.each
i .each_pair
iteracyjne nad każdej pary klucz-wartość:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each do |key, value|
puts "#{key} = #{value}"
end
# => first_name = John
# last_name = Doe
.each_key
iteruje tylko po klawiszach:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_key do |key|
puts key
end
# => first_name
# last_name
.each_value
iteruje tylko wartości:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_value do |value|
puts value
end
# => John
# Doe
.each_with_index
iteruje po elementach i zapewnia indeks iteracji:
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
Konwersja do iz tablic
Skróty można swobodnie konwertować do iz macierzy. Konwersja skrótu par klucz / wartość w tablicę spowoduje utworzenie tablicy zawierającej zagnieżdżone tablice dla pary:
{ :a => 1, :b => 2 }.to_a # => [[:a, 1], [:b, 2]]
W przeciwnym kierunku można utworzyć skrót z tablicy tego samego formatu:
[[:x, 3], [:y, 4]].to_h # => { :x => 3, :y => 4 }
Podobnie, skróty mogą być inicjowane za pomocą Hash[]
i listy na przemian kluczy i wartości:
Hash[:a, 1, :b, 2] # => { :a => 1, :b => 2 }
Lub z tablicy tablic z dwiema wartościami:
Hash[ [[:x, 3], [:y, 4]] ] # => { :x => 3, :y => 4 }
Skróty można przekonwertować z powrotem na tablicę naprzemiennych kluczy i wartości za pomocą flatten()
:
{ :a => 1, :b => 2 }.flatten # => [:a, 1, :b, 2]
Łatwa konwersja do iz tablicą pozwala Hash
do pracy również z wielu Enumerable
metod, takich jak collect
i 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 }
Uzyskiwanie wszystkich kluczy lub wartości skrótu
{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>
Przesłanianie funkcji skrótu
eql?
Ruby używają metod hash
i eql?
w celu wykonania operacji skrótu i przypisania obiektów przechowywanych w skrócie do wewnętrznych pojemników skrótu. Domyślną implementacją hash
w Ruby jest funkcja skrótu szmeru we wszystkich polach składowych obiektu mieszanego . Aby zastąpić to zachowanie, można przesłonić hash
i eql?
metody
Podobnie jak w przypadku innych implementacji skrótu, dwa obiekty aib zostaną zszyfrowane do tego samego segmentu, jeśli a. a.hash == b.hash
i zostaną uznane za identyczne, jeśli a. a.eql?(b)
. Tak więc, kiedy reimplementujemy hash
i eql?
należy zadbać o to, że jeśli i a
b
są równe pod eql?
muszą zwrócić tę samą wartość hash
. W przeciwnym razie może to spowodować zduplikowanie wpisów w haszu. I odwrotnie, zły wybór implementacji hash
może spowodować, że wiele obiektów będzie korzystać z tego samego segmentu mieszania, skutecznie niszcząc czas wyszukiwania O (1) i powodując wywołanie eql?
O (n) eql?
na wszystkich przedmiotach.
W poniższym przykładzie tylko instancja klasy A
jest przechowywana jako klucz, ponieważ została dodana jako pierwsza:
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
Filtrowanie skrótów
select
zwraca nowy hash
z parami klucz-wartość, dla których blok ma wartość true
.
{ :a => 1, :b => 2, :c => 3 }.select { |k, v| k != :a && v.even? } # => { :b => 2 }
Jeśli nie potrzebujesz klucza lub wartości w bloku filtra, konwencja polega na użyciu _
w tym miejscu:
{ :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
zwraca nowy hash
z parami klucz-wartość, dla których blok ocenia na 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 }
Ustaw operacje na skrótach
Przecięcie skrótów
Aby uzyskać przecięcie dwóch skrótów, zwróć klucze współdzielone, których wartości są równe:
hash1 = { :a => 1, :b => 2 } hash2 = { :b => 2, :c => 3 } hash1.select { |k, v| (hash2.include?(k) && hash2[k] == v) } # => { :b => 2 }
Łączenie (scalanie) skrótów:
klucze w skrócie są unikalne, jeśli klucz występuje w obu skrótach, które mają zostać scalone, jeden z skrótu wywoływanego przez
merge
jest zastępowany: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 }