Ruby Language
hashes
Sök…
Introduktion
A Hash är en ordbokliknande samling av unika nycklar och deras värden. Även kallad associativa matriser, de liknar Arrays, men där en array använder heltal som sitt index, med en Hash kan du använda valfri objekttyp. Du hämtar eller skapar en ny post i en Hash genom att hänvisa till dess nyckel.
Syntax
{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}
Anmärkningar
Hashes i Ruby-kartnycklar till värden med en hashtabell.
Alla hashbara objekt kan användas som nycklar. Det är emellertid mycket vanligt att använda en Symbol
eftersom den generellt sett är mer effektiv i flera Ruby-versioner på grund av den minskade objektallokeringen.
{ key1: "foo", key2: "baz" }
Skapa en hash
En hash i Ruby är ett objekt som implementerar en hashtabell och kartlägger nycklar till värden. Ruby stöder en specifik bokstavlig syntax för att definiera hascher med {}
:
my_hash = {} # an empty hash
grades = { 'Mark' => 15, 'Jimmy' => 10, 'Jack' => 10 }
En hash kan också skapas med den new
standardmetoden:
my_hash = Hash.new # any empty hash
my_hash = {} # any empty hash
Hashar kan ha värden av vilken typ som helst, inklusive komplexa typer som matriser, objekt och andra 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}
Även nycklar kan vara av valfri typ, inklusive komplexa sådana:
mapping = { 'Mark' => 15, 5 => 10, [1, 2] => 9 }
mapping['Mark'] # => 15
mapping[[1, 2]] # => 9
Symboler används ofta som hash-nycklar, och Ruby 1.9 introducerade en ny syntax specifikt för att förkorta denna process. Följande hashes är likvärdiga:
# 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 }
Följande hash (giltigt i alla Ruby-versioner) är annorlunda eftersom alla nycklar är strängar:
grades = { "Mark" => 15, "Jimmy" => 10, "Jack" => 10 }
Medan båda syntaxversionerna kan blandas, avskräcks följande.
mapping = { :length => 45, width: 10 }
Med Ruby 2.2+ finns det en alternativ syntax för att skapa en hash med symboltangenter (mest användbar om symbolen innehåller mellanslag):
grades = { "Jimmy Choo": 10, :"Jack Sparrow": 10 }
# => { :"Jimmy Choo" => 10, :"Jack Sparrow" => 10}
Få tillgång till värden
Enskilda värden för en hash läses och skrivs med metoderna []
och []=
:
my_hash = { length: 4, width: 5 }
my_hash[:length] #=> => 4
my_hash[:height] = 9
my_hash #=> {:length => 4, :width => 5, :height => 9 }
Som standard kommer åtkomst till en nyckel som inte har lagts till i hash nil
, vilket innebär att det alltid är säkert att försöka slå upp en nyckels värde:
my_hash = {}
my_hash[:age] # => nil
Hashes kan också innehålla nycklar i strängar. Om du försöker komma åt dem normalt kommer det bara att returnera en nil
, istället får du åtkomst till dem med strängnycklarna:
my_hash = { "name" => "user" }
my_hash[:name] # => nil
my_hash["name"] # => user
För situationer där nycklar förväntas eller måste existera, har hashes en fetch
som kommer att göra ett undantag när du kommer åt en nyckel som inte finns:
my_hash = {}
my_hash.fetch(:age) #=> KeyError: key not found: :age
fetch
accepterar ett standardvärde som sitt andra argument, som returneras om nyckeln inte har ställts in tidigare:
my_hash = {}
my_hash.fetch(:age, 45) #=> => 45
fetch
kan också acceptera ett block som returneras om nyckeln inte har ställts in tidigare:
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 stöder också en store
som ett alias för []=
:
my_hash = {}
my_hash.store(:age, 45)
my_hash #=> { :age => 45 }
Du kan också få alla värden på en hash med hjälp av values
:
my_hash = { length: 4, width: 5 }
my_hash.values #=> [4, 5]
Obs! Detta är bara för Ruby 2.3+ #dig
är praktiskt för kapslade Hash
. Extraherar det kapslade värdet som specificeras av sekvensen för idx-objekt genom att ringa dig vid varje steg och returnera noll om något mellansteg är noll.
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
Ställa in standardvärden
Som standard försöker slå värdet för en nyckel som inte existerar kommer tillbaka nil
. Du kan valfritt ange något annat värde som ska returneras (eller en åtgärd att vidta) när hash har åtkomst med en icke-existerande nyckel. Även om detta kallas "standardvärdet", behöver det inte vara ett enda värde; det kan till exempel vara ett beräknat värde som tangentens längd.
Standardvärdet för en hash kan överföras till dess konstruktör:
h = Hash.new(0)
h[:hi] = 1
puts h[:hi] # => 1
puts h[:bye] # => 0 returns default value instead of nil
En standard kan också anges på en redan konstruerad Hash:
my_hash = { human: 2, animal: 1 }
my_hash.default = 0
my_hash[:plant] # => 0
Det är viktigt att notera att standardvärdet inte kopieras varje gång en ny nyckel öppnas, vilket kan leda till överraskande resultat när standardvärdet är en referenstyp:
# 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']
För att kringgå detta problem accepterar Hash-konstruktören ett block som körs varje gång en ny nyckel öppnas och det returnerade värdet används som standard:
authors = Hash.new { [] }
# Note that we're using += instead of <<, see below
authors[:homer] += ['The Odyssey']
authors[:plato] # => []
authors # => {:homer=>["The Odyssey"]}
Observera att ovan var vi tvungna att använda + = istället för << eftersom standardvärdet inte automatiskt tilldelas hash; att använda << skulle ha lagt till i matrisen, men författare [: homer] skulle ha förblivit odefinierade:
authors[:homer] << 'The Odyssey' # ['The Odyssey']
authors[:homer] # => []
authors # => {}
För att kunna tilldela standardvärden vid åtkomst, såväl som att beräkna mer sofistikerade standardinställningar, passeras standardblocket både hash och nyckel:
authors = Hash.new { |hash, key| hash[key] = [] }
authors[:homer] << 'The Odyssey'
authors[:plato] # => []
authors # => {:homer=>["The Odyssey"], :plato=>[]}
Du kan också använda ett standardblock för att göra en åtgärd och / eller returnera ett värde beroende på nyckeln (eller annan information):
chars = Hash.new { |hash,key| key.length }
chars[:test] # => 4
Du kan till och med skapa mer komplexa hash:
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"}}
För att ställa in standardvärdet till en Proc på en redan befintlig hash, använd default_proc=
:
authors = {}
authors.default_proc = proc { [] }
authors[:homer] += ['The Odyssey']
authors[:plato] # => []
authors # {:homer=>["The Odyssey"]}
Skapa automatiskt en Deep Hash
Hash har ett standardvärde för nycklar som begärs men finns inte (noll):
a = {}
p a[ :b ] # => nil
När du skapar en ny Hash kan man ange standard:
b = Hash.new 'puppy'
p b[ :b ] # => 'puppy'
Hash.new tar också ett block, som gör att du automatiskt kan skapa kapslade hascher, till exempel Perls autovivifieringsbeteende eller 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 } } }
Ändra nycklar och värden
Du kan skapa en ny hash med tangenterna eller värdena ändrade, du kan också lägga till eller ta bort nycklar med hjälp av inject (AKA, reducera ). Till exempel för att producera en hash med strängade nycklar och stora versaler:
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 är en mängd, i huvudsak en samling av nyckel- / värdepar. Därför har metoder som each
, map
och inject
.
För varje nyckel- / värdepar i hash utvärderas det givna blocket, värdet på memo vid första körningen är frövärdet som skickas för att inject
, i vårt fall en tom hash, {}
. Värdet på memo
för efterföljande utvärderingar är det returnerade värdet för tidigare block utvärdering, det är därför vi modifierar memo
genom att ställa in en nyckel med ett värde och sedan returnera memo
i slutet. Returvärdet av den slutliga block utvärderingen är returvärdet av inject
, i vårt fall memo
.
För att undvika att behöva ange det slutliga värdet kan du använda each_with_object istället:
new_fruit = fruit.each_with_object({}) { |(k,v), memo| memo[k.to_s] = v.upcase }
Eller till och med karta :
new_fruit = Hash[fruit.map{ |k,v| [k.to_s, v.upcase] }]
(Se detta svar för mer information, inklusive hur man manipulerar hashes på plats.)
Iterating Over a Hash
En Hash
innehåller modulens Enumerable
, som tillhandahåller flera iterationsmetoder, såsom: Enumerable#each
, Enumerable#each_pair
, Enumerable#each_key
och Enumerable#each_value
.
.each
och .each_pair
iterera över varje nyckelvärdespar:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each do |key, value|
puts "#{key} = #{value}"
end
# => first_name = John
# last_name = Doe
.each_key
upprepas endast över nycklarna:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_key do |key|
puts key
end
# => first_name
# last_name
.each_value
iterates endast över värdena:
h = { "first_name" => "John", "last_name" => "Doe" }
h.each_value do |value|
puts value
end
# => John
# Doe
.each_with_index
iterates över elementen och ger indexet för iterationen:
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
Konvertering till och från Arrays
Hashar kan konverteras fritt till och från matriser. Om du konverterar en hash av nyckel- / värdepar till en array kommer det att producera en matris som innehåller kapslade matriser för par:
{ :a => 1, :b => 2 }.to_a # => [[:a, 1], [:b, 2]]
I motsatt riktning kan en Hash skapas från en matris med samma format:
[[:x, 3], [:y, 4]].to_h # => { :x => 3, :y => 4 }
På liknande sätt kan Hashes initialiseras med Hash[]
och en lista med alternerande nycklar och värden:
Hash[:a, 1, :b, 2] # => { :a => 1, :b => 2 }
Eller från en matris med två värden vardera:
Hash[ [[:x, 3], [:y, 4]] ] # => { :x => 3, :y => 4 }
Hashes kan konverteras tillbaka till en matris med alternerande nycklar och värden med flatten()
:
{ :a => 1, :b => 2 }.flatten # => [:a, 1, :b, 2]
Den enkla konverteringen till och från en array gör att Hash
kan fungera bra med många Enumerable
metoder som collect
och 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 }
Få alla nycklar eller värden för hash
{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>
Överväga hash-funktion
Ruby hashes använder metoderna hash
och eql?
att utföra hashoperationen och tilldela objekt lagrade i hash till interna hashfack. Standardimplementeringen av hash
i Ruby är murmur-hashfunktionen över alla medlemsfält för hash-objektet . För att åsidosätta detta beteende är det möjligt att åsidosätta hash
och eql?
metoder.
Som med andra hashimplementeringar kommer två objekt a och b att hashas till samma hink om a.hash == b.hash
och kommer att anses vara identiska om a.eql?(b)
. Således, när återimplementering av hash
och eql?
man bör se till att om a
och b
är lika med eql?
de måste returnera samma hash
. Annars kan detta resultera i duplicerade poster i en hash. Omvänt kan ett dåligt val i hash
många objekt att dela samma hashskopa, effektivt förstöra O (1) uppslagstid och orsaka O (n) för att ringa eql?
på alla objekt.
I exemplet nedan lagras endast instansen av klass A
som en nyckel, eftersom den lades till först:
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
Filtrera haskar
select
ger en ny hash
med nyckelvärdespar som blocket utvärderar till true
.
{ :a => 1, :b => 2, :c => 3 }.select { |k, v| k != :a && v.even? } # => { :b => 2 }
När du inte behöver nyckeln eller värdet i ett filterblock är konventionen att använda en _
på den platsen:
{ :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
returnerar en ny hash
med nyckelvärdespar som blocket utvärderar till 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 }
Ställ in operationer på Hashes
Korsning av Hashes
För att få skärningspunkten mellan två hascher, returnerar du de delade nycklarna vars värden är lika:
hash1 = { :a => 1, :b => 2 } hash2 = { :b => 2, :c => 3 } hash1.select { |k, v| (hash2.include?(k) && hash2[k] == v) } # => { :b => 2 }
Union (sammanslagning) av hashes:
nycklar i en hash är unika, om en nyckel inträffar i båda hasherna som ska slås samman skrivs den från hash som
merge
på: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 }