Ruby Tricks
The weirdest way to write an empty string
%%%%%%%
Explanation (try this out in pry
or irb
):
%( a string with the "%" syntax ) # => " a string with the \"%\" syntax " %! can use almost any character to delineate a string ! # => " can use almost any character to delineate a string " %% even a percent sign itself % # => " even a percent sign itself " %%% == "" # => true "strings also have a percent method %s" % "for formatting values" # => "strings also have a method for formatting values" "" % "" # => "" %%% % %%% # => "" %%%%%%% # => ""
Hash.new takes a block
Anything specified in the block to Hash.new
is considered to be the default value.
# true by default permissions = Hash.new { true } permissions[:jim] # => true permissions[:dwight] # => true permissions[:dwight] = false permissions[:dwight] # => false
More precisely, the block is called whenever a key lookup fails.
This has some interesting characteristics:
upper = Hash.new { |hash, key| key.upcase } upper['word'] # => "WORD" upper['class'] # => "CLASS" upper['iphone'] # => "IPHONE" upper['iphone'] = 'iPHONE' upper['iphone'] # => "iPHONE"
Two values are passed to the block, hash
(a reference to the hash itself), and key
.
You can use these values to set the value of a key at the moment the lookup fails:
# No need to check for nil observers = Hash.new { |hash, key| hash[key] = [] } observers # => {} observers[:event_a] << :listener_a observers[:event_a] << :listener_b observers[:event_b] << :listener_a observers # => { # :event_a => [:listener_a, :listener_b], # :event_b => [:listener_a] # }
You can use this trick to memoize:
def regular_ol_fib(n) return n if (0..1).include?(n) regular_ol_fib(n - 1) + regular_ol_fib(n - 2) end awesome_fib = Hash.new do |cache, n| cache[n] = awesome_fib[n - 2] + awesome_fib[n - 1] end awesome_fib[0] = 0 awesome_fib[1] = 1 awesome_fib[34] # took 0.047 milliseconds regular_ol_fib(34) # took 4.5 seconds awesome_fib[3000] # took 3.69 milliseconds regular_ol_fib(3000) # don't even try this
Autovivification
One interesting thing about the Hash.new
block is that you can get a reference to it from the hash instance itself:
hash_with_block = Hash.new { various_happenings } hash_with_block.default_proc # => #<Proc:0x007faf3ac93d08>
Because Hash.new
yields the hash itself, you can refer to the block inside the block itself:
autovivification = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) } autovivification[:keys][:dont][:even][:exist][:until] = :now autovivification # => {:keys=>{:dont=>{:even=>{:exist=>{:until=>:now}}}}}
(Auto means “self” and vivify means “give life to”.)
Shipping data with code
When you use the keyword __END__
, Ruby considers the script to be over at that point.
Anything written beyond it will be ignored:
# some ruby code __END__ this will never be interpreted
However, you can access this content with the DATA
object:
puts DATA.read # Will print "Hello there!" __END__ Hello there!
You can use this to include templates, data, or anything else you like along with your script.
quotes = DATA.to_a puts "Random Chuck Norris quote:" puts quotes.shuffle.pop __END__ When Chuck Norris throws exceptions, it’s across the room. All arrays Chuck Norris declares are of infinite size, because Chuck Norris knows no bounds. Chuck Norris doesn’t have disk latency because the hard drive knows to hurry the hell up. Chuck Norris writes code that optimizes itself. Chuck Norris can’t test for equality because he has no equal.