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.