Memo #3: Advanced usage of Ruby hashes and arrays

Posted by Dmytro Shteflyuk on under Ruby & Rails

Ruby 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.

6 Responses to this entry

Subscribe to comments with RSS

said on January 25th, 2009 at 03:59 · Permalink

What about . . . (for your example with h = Hash.new { |h, key| h[key] = [] }):

1
2
3
4
5
6
a = %w(apple banana apple)
h = Hash.new { [] }
a.each_with_index do |fruit, i|
  h[fruit] <<= i
end
puts h.inspect
said on January 25th, 2009 at 11:58 · Permalink

Or in this case (i.e. single default value):

1
h = Hash.new([])
said on January 25th, 2009 at 12:12 · Permalink

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.

said on January 25th, 2009 at 12:20 · Permalink

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)

Comments are closed

Comments for this entry are closed for a while. If you have anything to say – use a contact form. Thank you for your patience.