0

I have an array of

  • shop objects

    • which belong to city objects

      • which belong to prefecture objects

I'd like to end up with a hash listed by prefecture, then city, then frequency...

I came up with this, but it feels really un-rubylike..

city_by_prefecture = shop_list.reduce({}){ |h,e|
  if h[e.prefecture.name].nil?
    h[e.prefecture.name] = {e.city.name => 1}
  elsif h[e.prefecture.name][e.city.name].nil?
    h[e.prefecture.name][e.city.name] = 1
  else
    h[e.prefecture.name][e.city.name] += 1
  end
  h
}

There must be a DRY-er way to do this !

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
minikomi
  • 8,363
  • 3
  • 44
  • 51
  • possible duplicate of [How to assign hash\["a"\]\["b"\]= "c" if hash\["a"\] doesn't exist?](http://stackoverflow.com/questions/5878529/how-to-assign-hashab-c-if-hasha-doesnt-exist) – Andrew Grimm Aug 02 '11 at 08:00
  • 1
    You may want to use `h.has_key?(e.prefecture.name)` rather than `h[e.prefecture.name].nil?`, because that way it's more obvious what you're asking. Also, use `each_with_object` rather than `reduce`, so you don't have to put `h` at the end of the block. – Andrew Grimm Aug 02 '11 at 08:04
  • Thank you Andrew. I wasn't aware of `each_with_object`. – minikomi Aug 03 '11 at 05:35

2 Answers2

1
city_by_prefecture = shop_list.each_with_object({}){ |e,h|
  h[e.prefecture.name] ||= Hash.new(0)
  h[e.prefecture.name][e.city.name] += 1
}
Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
0
shops = [
  OpenStruct.new(:prefacture => "pre1", :city => "city1"), 
  OpenStruct.new(:prefacture => "pre1", :city => "city1"), 
  OpenStruct.new(:prefacture => "pre1", :city => "city2"), 
  OpenStruct.new(:prefacture => "pre2", :city => "city3"),
]

counts = Hash[shops.group_by(&:prefacture).map do |prefacture, shops_in_prefacture| 
  [prefacture, Hash[shops_in_prefacture.group_by(&:city).map do |city, shops_in_city| 
    [city, shops_in_city.size]
   end]] 
end]
# {"pre1"=>{"city1"=>2, "city2"=>1}, "pre2"=>{"city3"=>1}}
tokland
  • 66,169
  • 13
  • 144
  • 170