1
\$\begingroup\$

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?

\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

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)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.