One of the most used features in any programming language is a Hash. Today we are going to talk about some of the Ruby’s Hash features, which are well documented, but rarely used — parameters of the Hash constructor. In the second part of this article we will take a look at the arguments of the Array class’ constructor.
Take a look at the following example.
1 2 3 4 5 6 | a = %w(apple banana apple) h = a.inject({}) do |h, fruit| h[fruit] ||= 0 h[fruit] += 1 h end |
Here we have an array of fruits and we need to calculate a frequency of each fruit. As you can see, in the line 3 we are initializing frequency value to 0 if there are was no fruit with this name before. We can simplify this code:
1 2 3 4 5 | a = %w(apple banana apple) h = a.inject(Hash.new(0)) do |h, fruit| h[fruit] += 1 h end |
In line 2 we are creating a new hash, which default value is 0. This means that if we would try to retrieve value for a non-existing key, 0 would be returned.
Let’s check another example:
1 2 3 4 5 6 | a = %w(apple banana apple) h = {} a.each_with_index do |fruit, i| h[fruit] ||= [] h[fruit] << i end |
Here we are collecting indexes of each fruit in the source array. But now we can’t just create a new hash and pass [] as the default value, because all keys in this hash will refer to the same array, so in result we will get an array [1, 2, 3] for each fruit. So let’s try the following:
1 2 3 4 5 | a = %w(apple banana apple) h = Hash.new { |h, key| h[key] = [] } a.each_with_index do |fruit, i| h[fruit] << i end |
In this case we are creating a new array object for any non-existing key, that was accessed. So
1 | h['some non-existing key'] |
will return [] and save it in the hash. When you will hit this key next time, previously created array will be returned.
You can pass a block to Array constructor too. For example, you need an array with 10 random numbers:
1 2 | a = [] 10.times { a << rand(100) } |
You can simplify it using map method:
1 | a = (1..10).map { rand(100) } |
But you can do it even easier:
1 | a = Array.new(10) { rand(100) } |
Next Memo will cover managing Ruby Gems, so stay tuned.
What about . . . (for your example with h = Hash.new { |h, key| h[key] = [] }):
2
3
4
5
6
h = Hash.new { [] }
a.each_with_index do |fruit, i|
h[fruit] <<= i
end
puts h.inspect
Wow, it looks much better. Thank you!
Or in this case (i.e. single default value):
Peter, you code would not work. You will got the same array with numbers 1, 2, 3 for all fruits. Hash will be instantiated once and all keys will refer the same instance.
Ah sure… I didn’t notice that the elements of the original array are not unique (should have read the example better, counting the duplicates was the idea… doh)
I blogged about it too, a few months ago.
And Ilya Grigorik had an interesting post with some other nice Ruby fu’s.