Every Ruby developer loves their hashes, they are available in the core, O(1) (which means fast) for access, and they include the awesomest module in the world: Enumerable.
The fact, however that we are used to utilise the as mere dictionaries, or handy data aggregations whenever we can't be bothered to create a custom class.
That means that we usually only put "strings"
or :symbols
as keys. but hash keys are not limited to that, you can use anything that responds to .hash
method, and that in ruby is almost everything.
So thinking about this I came around to try to test the limits of this idea:
Basic usage of hash, with booleans as keys:
>> h = {true => '1 is odd', false => '1 is even'}
=> {true => "1 is odd", false => "1 is even"}
>> h[true]
=> "1 is odd"
Let's calculate the keys instead of using literal values
>> h = {1.odd? => '1 is odd', 1.even? => '1 is even' }
=> {true=>"1 is odd", false=>"1 is even"}
>> h[true]
=> "1 is odd"
Let's change 1.odd?
for p.odd?
so that it can be defined dynamically:
>> p = 1; h = { p.odd? => "#{p} is odd", p.even? => "#{p} is even" }
=> {true=>"1 is odd", false=>"1 is even"}
>> h[true]
=> "1 is odd"
>> p = 0; h = { p.odd? => "#{p} is odd", p.even? => "#{p} is even" }
=> {false=>"0 is odd", true=>"0 is even"}
>> h[true]
=> "0 is even"
But that only works defining p
before h
, so let's go one step further:
>> f = -> (p) { { p.odd? => 'odd', p.even? => 'even' }[true] }
=> #<Proc:0x00000002052bf0@(irb):49 (lambda)>
>> f[1]
=> "odd"
>> f[0]
=> "even"
And now, what if the procs from the keys could be more complex predicates, like nested procs:
>> f = -> (p) { { ->(a) { a.odd? }[p] => 'odd', ->(a) { a.even? }[p] => 'even' }[true] }
=> #<Proc:0x0000563ce10524d0@(irb):10 (lambda)>
>> f[1]
=> "odd"
>> f[2]
=> "even"
Are you dizzy about the braces? Let's put this some othe way:
>> f = proc do |p|
Hash.new.merge(
proc { |a| a.odd? }.call(p) => 'odd',
proc { |b| b.even? }.call(p) => 'even'
).fetch(true)
end
=> #<Proc:0x00000001fac070@(irb):52 (lambda)>
>> f[1]
=> "odd"
>> f[2]
=> "even"
You may as well tell me that this is a very convoluted way to tell the oddness of a number, but odd and even are here only to not distract from the explanation, the fact is that you can replace proc { |a| a.odd? }
for anything that accepts .call
with the appropriate parameters and have a very lean classifier for any disjoint discreet set.
This is not necessarily the best way to do this, you might rather use a case/when statement. But at least I hope you can leave today with a broader idea of ruby hashes and what can go on their keys.
P.S: salutations to @tholford0 who suffered this with no explanations as a whiteboard brain-teaser. (not an interview)
Top comments (1)
Thank you for the shout-out, @oinak . And nice write up! You consistently blow my mind with your arcane knowledge of Ruby :)