Ruby Question About # Signs

Questions on implementing hashes in ruby

Hash#sort has the same effect (at least for my basic test) as Hash#to_a followed by Array#sort.

hash = {b: 2, a: 1}
hash.to_a.sort # => [[:a, 1, [:b, 2]]
hash.sort # => the same

Now let's look at #each, both on Hash and Array.

When you provide two arguments to the block, that can handle both cases. For the hash, the first argument will be the key and the second will be the value. For the nested array, the values essentially get splatted out to the args:

[[:a, 1, 2], [:b, 3, 4]].each { |x, y, z| puts "#{x}-#{y}-#{z}" }
# => a-1-2
# => b-3-4

So basically, you should think of Hash#sort to be a shortcut to Hash#to_a followed by Array#sort, and recognize that #each will work the same on a hash as a hash converted to array (a nested array). In this case, it doesn't matter which approach you take. Clearly if you need to sort iteration by the keys then you should use sort.

Help explaining symbols as hash values in Ruby?

Here there are a hash (cities) with 3 pairs key => value

cities = {'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville'}

You can access that values by the key:

puts cities['CA']  #=>  'San Francisco'

Now we add two new pairs.

cities['NY'] = 'New York'
cities['OR'] = 'Portland'

The whole hash will be:

p cities #=> {'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville', 'NY' => 'New York', 'OR' => 'Portland'}

Now it's defined a method called find_city. It takes a hash and a key.

def find_city (map, state)
# if hash has the key, return its value. (There are better ways to do it.)
if map.include? state
return map[state]
else
return "Not found."
end
end

Here there is the worst piece of Ruby code I've ever seen.

cities[:find] = method(:find_city)

Ok, that code gets the method find_city and turns it into a Method object, which could be assigned to a variable. BUT, instead of using a normal local variable, it is stored in a value of the cities hash!

That object is similar to a method, but it's an object instead of a method, and it must be called with call (in Ruby 1.8.7).

I will use a variable called my_meth (it could be any name) to explain better.

# get the method find_city and turns it into an object assigned to my_meth
my_meth = method(:find_city)

while true
print "State? (Enter to quit) "
state = gets.chomp

break if state.empty?

# here we use find_city on the cities hash.
puts my_meth.call(cities, state)
end

puts my_meth.class

But, instead to use a variable, the original code stored the Method object on the cities hash. So, cities will be:

cities[:find] = method(:find_city)
p cities #=> {'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville', 'NY' => 'New York', 'OR' => 'Portland', :find => (the Method object)}

So, you can access the find_city thru cities[:find].call.

Confused about the million ways to reach inside Ruby Hash with the symbol sign

Rule of thumb

If there's a colon (:) it's a Symbol. If there's a hashrocket (=>), it's whatever is to the left of the hashrocket (which can be anything).

Declaring keys in a Hash literal

When we say "Hash literal" we mean code that declares a Hash with curly braces ({ foo: 1 }) or as method arguments (bar(baz: 2)).

There are two ways to declare a key in a Hash literal.

Keys with hashrockets (=>)

The first way to declare a key is with the hashrocket (=>). When you use the hashrocket, the key is whatever value is to the left of it, and you can put any kind of object (or expression) to the left of it:

hashrocket_hash = {
"I am a String" => 1,
:I_am_a_Symbol => 2,
:"I am also a Symbol" => 4,
/I am a Regexp!/ => 5,
Kernel => 6,
if true then "I am also a String" end => 7,
nil => 8
}

p hashrocket_hash.keys
# => [ "I am a String",
# :I_am_a_Symbol,
# :"I am also a Symbol",
# /I am a Regexp!/,
# Kernel,
# "I am also a String",
# nil
# ]

p hashrocket_keys.map(&:class)
# => [ String,
# Symbol,
# Symbol,
# Regexp,
# Module,
# String,
# NilClass
# ]

Keys with colons (:)

The other way to declare a key is with a colon (:). When you use a colon the resulting key is always a Symbol. The usual Symbol rules apply (read the very thorough answer here: What can a ruby symbol (syntax) contain?) except the colon goes at the end instead of the beginning:

colon_hash = {
I_am_a_Symbol: 9,
"I am also a Symbol": 10
}

p colon_hash.keys
# => [ :I_am_a_Symbol,
# :"I am also a Symbol" ]

p colon_hash.keys.map(&:class)
# => [ Symbol,
# Symbol ]

Accessing a Hash value

There are no special rules for accessing a Hash value. If you want to access a value whose key is a Symbol, you must use a Symbol. If you want to access a value whose key is a String, you must use a String. If the key is something else, you must use that thing.

In the below examples, pay close attention to which keys are followed by colons and which are followed by hashrockets:

hsh1 = { foo: 1 }
p hsh1[:foo] # => 1
p hsh1[:"foo"] # => 1
p hsh1["foo"] # => nil

hsh2 = { "bar": 2 }
p hsh2[:bar] # => 2
p hsh2[:"bar"] # => 2
p hsh2["bar"] # => nil

hsh3 = {
Kernel: 3,
Kernel => 4
}
p hsh3[:Kernel] # => 3
p hsh3[Kernel] # => 4
p hsh3["Kernel"] # => nil

Why use symbols as hash keys in Ruby?

TL;DR:

Using symbols not only saves time when doing comparisons, but also saves memory, because they are only stored once.

Ruby Symbols are immutable (can't be changed), which makes looking something up much easier

Short(ish) answer:

Using symbols not only saves time when doing comparisons, but also saves memory, because they are only stored once.

Symbols in Ruby are basically "immutable strings" .. that means that they can not be changed, and it implies that the same symbol when referenced many times throughout your source code, is always stored as the same entity, e.g. has the same object id.

Strings on the other hand are mutable, they can be changed anytime. This implies that Ruby needs to store each string you mention throughout your source code in it's separate entity, e.g. if you have a string "name" multiple times mentioned in your source code, Ruby needs to store these all in separate String objects, because they might change later on (that's the nature of a Ruby string).

If you use a string as a Hash key, Ruby needs to evaluate the string and look at it's contents (and compute a hash function on that) and compare the result against the (hashed) values of the keys which are already stored in the Hash.

If you use a symbol as a Hash key, it's implicit that it's immutable, so Ruby can basically just do a comparison of the (hash function of the) object-id against the (hashed) object-ids of keys which are already stored in the Hash. (much faster)

Downside:
Each symbol consumes a slot in the Ruby interpreter's symbol-table, which is never released.
Symbols are never garbage-collected.
So a corner-case is when you have a large number of symbols (e.g. auto-generated ones). In that case you should evaluate how this affects the size of your Ruby interpreter.

Notes:

If you do string comparisons, Ruby can compare symbols just by comparing their object ids, without having to evaluate them. That's much faster than comparing strings, which need to be evaluated.

If you access a hash, Ruby always applies a hash-function to compute a "hash-key" from whatever key you use. You can imagine something like an MD5-hash. And then Ruby compares those "hashed keys" against each other.

Every time you use a string in your code, a new instance is created - string creation is slower than referencing a symbol.

Starting with Ruby 2.1, when you use frozen strings, Ruby will use the same string object. This avoids having to create new copies of the same string, and they are stored in a space that is garbage collected.

Long answers:

https://web.archive.org/web/20180709094450/http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings

http://www.randomhacks.net.s3-website-us-east-1.amazonaws.com/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/

https://www.rubyguides.com/2016/01/ruby-mutability/

What are = and : in Ruby?

Lexer/Parser Tokens

The symbols you're referencing aren't methods or operators, they are lexer/parser tokens used to interpret the syntax of your source code. The hashrocket is defined as the tASSOC association token, which is used to associate things such as key/value pairs or exception stack traces.

The colon has several uses in Ruby, but IIRC Ruby 2.x introduced the postfix colon as syntactic sugar for tASSOC when the left-hand side is a Symbol. I'm less sure about how the token is defined or parsed in complex cases—assoc is the most likely bet for this example—but for practical purposes you can simply think of a: 1 as semantically equivalent to :a => 1.

You can also use Ripper#sexp to examine your source code to see how the lines will be parsed by the interpreter. For example:

require 'ripper'

pp Ripper.sexp "{a: 1}"
[:program,
[[:hash,
[:assoclist_from_args,
[[:assoc_new, [:@label, "a:", [1, 1]], [:@int, "1", [1, 4]]]]]]]]
#=> [:program, [[:hash, [:assoclist_from_args, [[:assoc_new, [:@label, "a:", [1, 1]], [:@int, "1", [1, 4]]]]]]]]

pp Ripper.sexp "{:a => 1}"
[:program,
[[:hash,
[:assoclist_from_args,
[[:assoc_new,
[:symbol_literal, [:symbol, [:@ident, "a", [1, 2]]]],
[:@int, "1", [1, 7]]]]]]]]
#=> [:program, [[:hash, [:assoclist_from_args, [[:assoc_new, [:symbol_literal, [:symbol, [:@ident, "a", [1, 2]]]], [:@int, "1", [1, 7]]]]]]]]

In both cases, you can see that the S-expression is using the colon to build an "assoc_new" subexpression. For further drill-down, you'd have to refer to the Ruby source tree.

See Also

  • lexer.rb
  • parse.y

Using Key Value pairs in Hash as question and answer

Use Hash#each to loop through each pair of countries and capitals. In that loop, use Kernel#gets to read their answer and String#chomp to remove the newline off their answer.

cos_n_caps.each do |country,capital|
puts "What is the capital of #{country}?"
answer = gets.chomp

if capital.downcase == answer.downcase
puts "Right!"
else
puts "Sorry, it's #{capital}."
end
end

Ruby syntax with hash keys and symbols

The notation introduced in Ruby 1.9 is just a shortcut, and you can see what it means using irb:

h = { dependent: :destroy }
# => { :dependent => :destroy }

They're both symbols. Don't forget that a hash can be keyed by any object, not necessarily a symbol or a string. This is completely different from most languages where the key will be coerced into something consistent.

Using that example you can see what the types of the keys and values are:

h.keys
# => [:dependent]

h.values
# => [:destroy]

They're all symbols in this case.

Ruby Symbols vs Strings in Hashes

Since a symbol is not the same as a string:

:url == 'url' #=> false

As hash keys they would be different. Perhaps you have seen this behavior in Rails? Ruby on Rails uses HashWithIndifferentAccess which maps everything to a String internally, so you can do this:

h = HashWithIndifferentAccess.new
h['url'] = 'http://www.google.com/'
h[:url] #=> 'http://www.google.com/'


Related Topics



Leave a reply



Submit