DEV Community

Oinak
Oinak

Posted on

Abusing Ruby Hashes for fun

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"

Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
tholford profile image
Tom

Thank you for the shout-out, @oinak . And nice write up! You consistently blow my mind with your arcane knowledge of Ruby :)