Ruby’s identity method
When Ruby 2.2 added #itself
I couldn’t think of anything I’d previously encountered where it would have been useful but I’ve finally used it in the wild. I had an array of integers and wanted to count their occurrences. Initially reaching for each_with_object
I was hoping for something more meaningful, intention-revealing, and succinct.
[3, 1, 2, 1, 5].each_with_object(Hash.new(0)) { |i, memo| memo[i] += 1 }
# => {3=>1, 1=>2, 2=>1, 5=>1}
I remembered something about an identity method — #itself
— and by using it you get the following (which is definitely more succinct):
[3, 1, 2, 1, 5].group_by(&:itself)
# => {3=>[3], 1=>[1, 1], 2=>[2], 5=>[5]}
So now I could write a method to extract pairs:
def pairs(array)
array
.group_by(&:itself)
.select { |_, v| v.size == 2 }
end
pairs([3, 1, 2, 1, 5])
# => {1=>[1, 1]}
pairs([9, 1, 2, 2, 9])
# => {9=>[9, 9], 2=>[2, 2]}
In particular I wanted to check whether the array contained two groups each containing two items:
def two_pairs?(array)
pairs(array).size == 2
end
two_pairs?([3, 1, 2, 1, 5])
# => false
two_pairs?([9, 1, 2, 2, 9])
# => true
Enumerable#tally
Since I wrote this Ruby 2.7 was released and added Enumerable#tally
which behaves exactly like my initial code to count occurrences using #each_with_object
:
[3, 1, 2, 1, 5].tally
# => {3=>1, 1=>2, 2=>1, 5=>1}
Pre-#itself
hack
Looking around the internet it turns out that people were already getting around the lack of an identity method by calling a no-op method that returns self:
[3, 1, 2, 1, 5].group_by(&:to_i)
# => {3=>[3], 1=>[1, 1], 2=>[2], 5=>[5]}
self
and #itself
, what’s the difference?
To be honest although I expected it to fail I had my fingers crossed trying to use self
like so:
# This doesn’t work.
[3, 1, 2, 1, 5].group_by(&:self)
# => NoMethodError (undefined method `self' for 3:Integer)
It doesn’t work but I didn’t know why. It turns out that self
isn’t a method, it’s a special keyword whereas #itself
is a method defined on Object
(although the Ruby 2.2 release notes say it’s defined on Kernel
).
Updated 2021-02-19: Added the section on Enumerable#tally
.