1

I've been trying unsuccessfully to solve a problem I was asked during an interview, it was exactly like this:

Consider the following structure in Ruby:

['dog', 'donkey', 'cat', 'cow', 'horse']

How would you turn it into this one:

{ 'd' => ['dog', 'donkey'], 'c' => ['cat', 'cow'], 'h' => ['horse'] }

Being as idiomatic as possible ?

I have tried a lot of ways, and only have gotten close, and also have seen many similar problems around, but never a solution to this one in particular,

How would you guys do it? Can you help me solve it?

Best Regards,

jlstr
  • 2,986
  • 6
  • 43
  • 60

3 Answers3

10

Group by the first character of your words:

['dog', 'donkey', 'cat', 'cow', 'horse'].group_by{|i|i[0]}

or being a little bit fancier:

['dog', 'donkey', 'cat', 'cow', 'horse'].group_by &:chr
Howard
  • 38,639
  • 9
  • 64
  • 83
  • Oh my god this one is awesome ! although the guy during the interview said: "Look in methods like inject or each_with_object" – jlstr Aug 07 '11 at 14:44
  • Okay, so you guys have Scheme-like symbols that can be used to look up methods by name the same as you would with strings... and `chr` is a method on strings to return the first character... that last bit seems awfully strange to me (I'm coming from the Python world here; I can see how it bears a vague resemblance to what `chr` on ints does; but the original model for `chr` AFAIK is BASIC, which is why Python implements it as a free function and expects an integer...) – Karl Knechtel Aug 07 '11 at 15:06
  • @tokland This won't work. `String.first` is not standard ruby. – Howard Aug 07 '11 at 15:58
  • @Howard, indeed, String#first is from activesupport, I forgot. – tokland Aug 07 '11 at 16:27
4

If you have to build your own:

animals = ['dog', 'donkey', 'cat', 'cow', 'horse']
animals.inject(Hash.new{|h,k|h[k]=[]}) { |h, animal| h[animal[0]] << animal;h} 
#=> {"d"=>["dog", "donkey"], "c"=>["cat", "cow"], "h"=>["horse"]}

Main advantage is that you only have to traverse the array once. If you find inject hard to digest, look at 1.9's each_with_object. As others pointed out they probably wanted group_by though.

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • There's no indication in the docs. Maybe it gets deprecated in Rails because it became part of core Ruby in 1.9? – Michael Kohl Aug 07 '11 at 14:45
  • Anyways, Your solution IS the way the guy in the interview wanted. because it's using inject, instead of groupby. Excellent lesson, thank you Michael! sincerely. – jlstr Aug 07 '11 at 14:48
  • Could you explain to me a little bit more about the parameter passed to inject: **Hash.new{|h,k|h[k]=[]}** You create one Hash only, am I right? or actually one per each letter? I'm a bit confused. – jlstr Aug 07 '11 at 14:59
  • @user766388 `Hash.new{|h,k|h[k]=[]}` creates a new hash, but whenever a new entry in the hash has to be created (because it is accessed somewhere in the code) the block is called with the hash as first and the key as second arg. In this case a new entry (empty array) is then added to the hash for the key. – Howard Aug 07 '11 at 15:04
0

Take a look at http://api.rubyonrails.org/classes/Enumerable.html#method-i-group_by. It's a core_ext in Rails. It could be used to do exactly what you have to do. Reading the source of the method you get a good example of how you can achieve that.

lucapette
  • 20,564
  • 6
  • 65
  • 59