Sign up ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I have an array of thing objects, each of which has an array of categories. I'd like to build a category hash with a category ID for a key and an array of things for a value.

category_hash = Hash.new

things.each { |thing|
  thing.categories.each { |category|
    category_hash[category] = Array.new unless category_hash[category]
    category_hash[category] << thing
  }
}

Purely for education, how can this be shortened?

share|improve this question

migrated from stackoverflow.com Oct 13 '14 at 12:41

This question came from our site for professional and enthusiast programmers.

1 Answer 1

There are several ways of making this more idiomatic:

  • A trivial note, but don't use Array.new.

    Use [], it's shorter, clearer, and more idiomatic

  • Don't use category_hash[category] = [] unless category_hash[category]

    Instead, use category_hash[category] ||= []. In general you can use a ||= b instead of a = b unless a. In the case of hashes, you can skip this completely and just give the hash an appropriate default value:

    category_hash = Hash.new { |hash,key| hash[key] = [] }
    
  • Don't initialize a collection to an empty state, and then iterate over another collection, appending to the new collection. Use map/each_with_object/inject/etc. to turn one collection into another collection

A more idiomatic solution might look like this:

category_hash = things.each_with_object(Hash.new {|h,k| h[k] = [] }) do |thing,hash|
  thing.categories.each { |cat| hash[cat] << thing }
end

A completely different approach would be to map each thing's categories into a new hash, and then merge the resulting hashes. The problem is that you need a "deep" merge, like the one that comes with ActiveSupport:

categories_hash = things.map do |thing|
  Hash[thing.categories.map { |cat| [cat,thing] }]
end.reduce(&:deep_merge)
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.