Ruby Language
hashes
Zoeken…
Invoering
Een hash is een woordenboekachtige verzameling unieke sleutels en hun waarden. Ze worden ook associatieve arrays genoemd en zijn vergelijkbaar met arrays, maar waar een array gehele getallen als index gebruikt, kunt u met een hash elk objecttype gebruiken. U haalt een nieuw item op in een hash of maakt er een aan door naar de sleutel te verwijzen.
Syntaxis
{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}
Opmerkingen
Hasht in Ruby-kaartsleutels naar waarden met behulp van een hashtabel.
Elk hash-object kan als sleutels worden gebruikt. Het is echter heel gebruikelijk om een Symbol
te gebruiken, omdat het over het algemeen efficiënter is in verschillende Ruby-versies vanwege de verminderde objecttoewijzing.
{ key1: "foo", key2: "baz" }
Een hash maken
Een hash in Ruby is een object dat een hashtabel implementeert en sleutels toewijst aan waarden. Ruby ondersteunt een specifieke letterlijke syntaxis voor het definiëren van hashes met {}
:
my_hash = {} # an empty hash
grades = { 'Mark' => 15, 'Jimmy' => 10, 'Jack' => 10 }
Een hash kan ook worden gemaakt met de standaard new
methode:
my_hash = Hash.new # any empty hash
my_hash = {} # any empty hash
Hashes kunnen waarden van elk type hebben, inclusief complexe typen zoals arrays, objecten en andere hashes:
mapping = { 'Mark' => 15, 'Jimmy' => [3,4], 'Nika' => {'a' => 3, 'b' => 5} }
mapping['Mark'] # => 15
mapping['Jimmy'] # => [3, 4]
mapping['Nika'] # => {"a"=>3, "b"=>5}
Sleutels kunnen ook van elk type zijn, inclusief complexe:
mapping = { 'Mark' => 15, 5 => 10, [1, 2] => 9 }
mapping['Mark'] # => 15
mapping[[1, 2]] # => 9
Symbolen worden vaak gebruikt als hashsleutels en Ruby 1.9 introduceerde een nieuwe syntaxis specifiek om dit proces te verkorten. De volgende hashes zijn gelijkwaardig:
# 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 }
De volgende hash (geldig in alle Ruby-versies) is anders , omdat alle sleutels tekenreeksen zijn:
grades = { "Mark" => 15, "Jimmy" => 10, "Jack" => 10 }
Hoewel beide syntaxisversies kunnen worden gemengd, wordt het volgende afgeraden.
mapping = { :length => 45, width: 10 }
Met Ruby 2.2+ is er een alternatieve syntaxis voor het maken van een hash met symbooltoetsen (handig als het symbool spaties bevat):
grades = { "Jimmy Choo": 10, :"Jack Sparrow": 10 }
# => { :"Jimmy Choo" => 10, :"Jack Sparrow" => 10}
Toegang tot waarden
Individuele waarden van een hash worden gelezen en geschreven met de methoden []
en []=
:
my_hash = { length: 4, width: 5 }
my_hash[:length] #=> => 4
my_hash[:height] = 9
my_hash #=> {:length => 4, :width => 5, :height => 9 }
Toegang tot een sleutel die niet is toegevoegd aan de hash retourneert standaard nil
, wat betekent dat het altijd veilig is om te proberen de waarde van een sleutel op te zoeken:
my_hash = {}
my_hash[:age] # => nil
Hashes kunnen ook sleutels in tekenreeksen bevatten. Als je probeert ze normaal te openen, zal het gewoon nil
opleveren, in plaats daarvan krijg je toegang door hun string-toetsen:
my_hash = { "name" => "user" }
my_hash[:name] # => nil
my_hash["name"] # => user
Voor situaties waarin sleutels worden verwacht of vereist zijn, hebben hashes een fetch
die een uitzondering genereert bij toegang tot een sleutel die niet bestaat:
my_hash = {}
my_hash.fetch(:age) #=> KeyError: key not found: :age
fetch
accepteert een standaardwaarde als tweede argument, die wordt geretourneerd als de sleutel niet eerder is ingesteld:
my_hash = {}
my_hash.fetch(:age, 45) #=> => 45
fetch
kan ook een blok accepteren dat wordt geretourneerd als de sleutel niet eerder is ingesteld:
my_hash = {}
my_hash.fetch(:age) { 21 } #=> 21
my_hash.fetch(:age) do |k|
puts "Could not find #{k}"
end
#=> Could not find age
Hashes ondersteunt ook een store
als alias voor []=
:
my_hash = {}
my_hash.store(:age, 45)
my_hash #=> { :age => 45 }
Je kunt ook alle waarden van een hash ophalen met behulp van de values
:
my_hash = { length: 4, width: 5 }
my_hash.values #=> [4, 5]
Opmerking: dit is alleen voor Ruby #dig
is handig voor geneste Hash
's. Extraheert de geneste waarde die is opgegeven door de reeks idx-objecten door bij elke stap dig aan te roepen, waarbij nul wordt geretourneerd als een tussenstap nul is.
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
Standaardwaarden instellen
Als u de waarde probeert op te zoeken voor een sleutel die niet bestaat, wordt standaard nil
geretourneerd. U kunt optioneel een andere waarde opgeven die moet worden geretourneerd (of een actie die moet worden ondernomen) wanneer de hash wordt geopend met een niet-bestaande sleutel. Hoewel dit "de standaardwaarde" wordt genoemd, hoeft het geen enkele waarde te zijn; het kan bijvoorbeeld een berekende waarde zijn, zoals de lengte van de sleutel.
De standaardwaarde van een hash kan worden doorgegeven aan de constructor:
h = Hash.new(0)
h[:hi] = 1
puts h[:hi] # => 1
puts h[:bye] # => 0 returns default value instead of nil
Een standaard kan ook worden opgegeven voor een reeds gebouwde Hash:
my_hash = { human: 2, animal: 1 }
my_hash.default = 0
my_hash[:plant] # => 0
Het is belangrijk op te merken dat de standaardwaarde niet wordt gekopieerd telkens wanneer een nieuwe sleutel wordt gebruikt, wat kan leiden tot verrassende resultaten wanneer de standaardwaarde een referentietype is:
# 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']
Om dit probleem te omzeilen, accepteert de Hash-constructor een blok dat wordt uitgevoerd telkens wanneer een nieuwe sleutel wordt gebruikt en de geretourneerde waarde als standaard wordt gebruikt:
authors = Hash.new { [] }
# Note that we're using += instead of <<, see below
authors[:homer] += ['The Odyssey']
authors[:plato] # => []
authors # => {:homer=>["The Odyssey"]}
Merk op dat we hierboven + = moesten gebruiken in plaats van << omdat de standaardwaarde niet automatisch wordt toegewezen aan de hash; met behulp van << zou aan de array zijn toegevoegd, maar auteurs [: homer] zouden ongedefinieerd zijn gebleven:
authors[:homer] << 'The Odyssey' # ['The Odyssey']
authors[:homer] # => []
authors # => {}
Om standaardwaarden bij toegang toe te wijzen en om meer geavanceerde standaardwaarden te berekenen, wordt het standaardblok zowel de hash als de sleutel doorgegeven:
authors = Hash.new { |hash, key| hash[key] = [] }
authors[:homer] << 'The Odyssey'
authors[:plato] # => []
authors # => {:homer=>["The Odyssey"], :plato=>[]}
U kunt ook een standaardblok gebruiken om een actie uit te voeren en / of een waarde terug te geven die afhankelijk is van de sleutel (of andere gegevens):
chars = Hash.new { |hash,key| key.length }
chars[:test] # => 4
Je kunt zelfs complexere hashes maken:
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"}}
Gebruik default_proc=
om de standaardwaarde in te stellen op een Proc op een reeds bestaande hash:
authors = {}
authors.default_proc = proc { [] }
authors[:homer] += ['The Odyssey']
authors[:plato] # => []
authors # {:homer=>["The Odyssey"]}
Automatisch een Deep Hash maken
Hash heeft een standaardwaarde voor sleutels die worden aangevraagd maar niet bestaan (nul):
a = {}
p a[ :b ] # => nil
Bij het maken van een nieuwe hash kan men de standaard opgeven:
b = Hash.new 'puppy'
p b[ :b ] # => 'puppy'
Hash.new neemt ook een blok, waarmee je automatisch geneste hashes kunt maken, zoals Perl's autovivification-gedrag of 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 } } }
Sleutels en waarden wijzigen
Je kunt een nieuwe hash maken met de sleutels of waarden gewijzigd, je kunt inderdaad ook sleutels toevoegen of verwijderen met inject (AKA, verkleinen ). Om bijvoorbeeld een hash te produceren met stringed toetsen en hoofdletters:
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 is een opsomming, in wezen een verzameling sleutel / waarde-paren. Daarom is het heeft methoden zoals each
, map
en inject
.
Voor elk sleutel / waarde-paar in de hash wordt het gegeven blok geëvalueerd, de waarde van memo bij de eerste run is de seed-waarde die wordt doorgegeven om te inject
, in ons geval een lege hash, {}
. De waarde van memo
voor volgende evaluaties is de geretourneerde waarde van de vorige blokkenevaluatie, dit is de reden waarom we memo
wijzigen door een sleutel met een waarde in te stellen en vervolgens memo
aan het einde retourneren. De retourwaarde van de evaluatie van de laatste blokken is de retourwaarde van inject
, in ons geval memo
.
Om te voorkomen dat u de uiteindelijke waarde moet opgeven , kunt u in plaats daarvan elk_met_object gebruiken:
new_fruit = fruit.each_with_object({}) { |(k,v), memo| memo[k.to_s] = v.upcase }
Of zelfs kaart :
new_fruit = Hash[fruit.map{ |k,v| [k.to_s, v.upcase] }]
(Zie dit antwoord voor meer informatie, inclusief hoe je hashes op zijn plaats kunt manipuleren.)
Iets afsnijden over een hash
Een Hash
omvat de Enumerable
module, die verschillende iteratie werkwijzen, zoals bepaald: Enumerable#each
, Enumerable#each_pair
, Enumerable#each_key
en Enumerable#each_value
.
.each
en .each_pair
itereren over elk sleutel / waarde-paar:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each do |key, value|
puts "#{key} = #{value}"
end
# => first_name = John
# last_name = Doe
.each_key
herhaalt alleen de sleutels:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_key do |key|
puts key
end
# => first_name
# last_name
.each_value
herhaalt alleen de waarden:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_value do |value|
puts value
end
# => John
# Doe
.each_with_index
itereert over de elementen en geeft de index van de iteratie:
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
Conversie van en naar arrays
Hashes kunnen vrij worden omgezet van en naar arrays. Het omzetten van een hash van sleutel / waarde-paren in een array levert een array met geneste arrays voor paar:
{ :a => 1, :b => 2 }.to_a # => [[:a, 1], [:b, 2]]
In de tegenovergestelde richting kan een hash worden gemaakt op basis van een array met hetzelfde formaat:
[[:x, 3], [:y, 4]].to_h # => { :x => 3, :y => 4 }
Op dezelfde manier kunnen Hashes worden geïnitialiseerd met Hash[]
en een lijst met afwisselende sleutels en waarden:
Hash[:a, 1, :b, 2] # => { :a => 1, :b => 2 }
Of uit een reeks arrays met elk twee waarden:
Hash[ [[:x, 3], [:y, 4]] ] # => { :x => 3, :y => 4 }
Hashes kunnen worden omgezet in een array van afwisselende sleutels en waarden met flatten()
:
{ :a => 1, :b => 2 }.flatten # => [:a, 1, :b, 2]
Dankzij de eenvoudige conversie van en naar een array kan Hash
goed werken met vele Enumerable
methoden, zoals collect
en 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 }
Alle sleutels of waarden van hash ophalen
{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-functie overschrijven
Ruby-hashes gebruiken de methoden hash
en eql?
om de hash-bewerking uit te voeren en objecten die zijn opgeslagen in de hash toe te wijzen aan interne hashbins. De standaardimplementatie van hash
in Ruby is de murmur-hashfunctie over alle ledenvelden van het hashed-object . Om dit gedrag te onderdrukken is het mogelijk om hash
en eql?
te negeren eql?
methoden.
Net als bij andere hash-implementaties, worden twee objecten a en b naar dezelfde bucket a.hash == b.hash
als a.hash == b.hash
en worden ze als identiek beschouwd als a.eql?(b)
. Dus bij het opnieuw implementeren van hash
en eql?
moet men ervoor zorgen dat als a
en b
gelijk zijn onder eql?
ze moeten dezelfde hash
teruggeven. Anders kan dit resulteren in dubbele vermeldingen in een hash. Omgekeerd kan een slechte keuze in hash
implementatie ertoe leiden dat veel objecten dezelfde hash-bucket delen, waardoor de O (1) eql?
effectief wordt vernietigd en O (n) wordt veroorzaakt voor het aanroepen van eql?
op alle objecten.
In het onderstaande voorbeeld wordt alleen het exemplaar van klasse A
opgeslagen als een sleutel, omdat deze eerst is toegevoegd:
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
Hashes filteren
select
retourneert een nieuwe hash
met sleutel / waarde-paren waarvoor het blok de waarde true
.
{ :a => 1, :b => 2, :c => 3 }.select { |k, v| k != :a && v.even? } # => { :b => 2 }
Wanneer u de sleutel of waarde in een filterblok niet nodig hebt, is de conventie om op die plaats een _
te gebruiken:
{ :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
retourneert een nieuwe hash
met sleutel / waarde-paren waarvoor het blok als false
evalueert:
{ :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 }
Bewerkingen op hash instellen
Kruising van hashes
Om het snijpunt van twee hashes te krijgen, retourneert u de gedeelde sleutels waarvan de waarden gelijk zijn:
hash1 = { :a => 1, :b => 2 } hash2 = { :b => 2, :c => 3 } hash1.select { |k, v| (hash2.include?(k) && hash2[k] == v) } # => { :b => 2 }
Union (samenvoegen) van hashes:
sleutels in een hash zijn uniek, als een sleutel voorkomt in beide hashes die moeten worden samengevoegd, wordt die van de hash die
merge
wordt aangeroepen overschreven: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 }