Reputation: 27
I have this array of cities:
Bakersfield, California
Interstates: ["I-5"]
Oakland, California
Interstates: ["I-80"]
Atlanta, Georgia
Interstates: ["I-20", "I-75", "I-86"]
Cleveland, Ohio
Interstates: ["I-71", "I-77", "I-80", "I-90"]
Arlington, Texas
Interstates: ["I-20", "I-30"]
The name, the state and the interstates array are properties of each city.
I want to group them by their interstates so the final result would look something like this:
I-20: [Arlington, Atlanta]
I-5: [Bakersfield]
I-86: [Atlanta]
...
Is there a quick way to do this?
EDIT: Here is the true array, just as @wurde example.
cities = {
'Bakersfield' => {
state: 'California',
interstate: ['I-5']
},
'Oakland' => {
state: 'California',
interstate: ['I-80']
},
'Atlanta' => {
state: 'Georgia',
interstate: ["I-20", "I-75", "I-86"]
},
'Cleveland' => {
state: 'Ohio',
interstate: ["I-71", "I-77", "I-80", "I-90"]
},
'Arlington' => {
state: 'Texas',
interstate: ["I-20", "I-30"]
}
}
Upvotes: 0
Views: 84
Reputation: 7655
We have:
cities = {
'Bakersfield' => ['I-5'],
'Oakland' => ['I-80'],
'Atlanta' => ["I-20", "I-75", "I-86"],
'Cleveland' => ["I-71", "I-77", "I-80", "I-90"],
'Arlington' => ["I-20", "I-30"]
}
Longer version We can get what you want by this operations:
require 'set'
interstates = cities.inject(Set.new){|all,item| all+item[1]} # => #<Set: {"I-5", "I-80", "I-20", "I-75", "I-86", "I-71", "I-77", "I-90", "I-30"}>
result = interstates.map{|inter| [inter, cities.select{|_,interstates| interstates.include?(inter)}.keys]}.to_h # => {"I-5"=>["Bakersfield"], "I-80"=>["Oakland", "Cleveland"], "I-20"=>["Atlanta", "Arlington"], "I-75"=>["Atlanta"], "I-86"=>["Atlanta"], "I-71"=>["Cleveland"], "I-77"=>["Cleveland"], "I-90"=>["Cleveland"], "I-30"=>["Arlington"]}
We use Set
because it's efficient in given settings (for more elegant syntax, see note below). On line 2 we get all interstates
using Enumerable
's inject
method. Final result is obtained on line 3, where we use mapping and filtering (map
and select
methods). Note, that to_h
method is available from Ruby 2.1. If you are on older Ruby, you can convert mapped array to Hash
using Hash[array]
.
One-liner It can even be written as one-liner:
cities.inject(Set.new){|all,item| all+item[1]}.map{|inter| [inter, cities.select{|_,interstates| interstates.include?(inter)}.keys]}.to_h
Note I included Set
here for performance. If you prefer readability, then it's much shorter simply write interstates = cities.values.flatten.uniq
, as @engineersmnky noted in comment.
Upvotes: 0
Reputation: 110685
Using @wurde's example (thank you, thank you, thank you):
cities.each_with_object({}) do |(k,g),h|
g[:interstate].each { |i| h.update(i=>[k]) { |_,o,n| o+n } }
end
#=> {"I-5" =>["Bakersfield"],
# "I-80"=>["Oakland", "Cleveland"],
# "I-20"=>["Atlanta", "Arlington"],
# "I-75"=>["Atlanta"],
# "I-86"=>["Atlanta"],
# "I-71"=>["Cleveland"],
# "I-77"=>["Cleveland"],
# "I-90"=>["Cleveland"],
# "I-30"=>["Arlington"]}
This uses the form of Hash#update (aka merge!
) that uses the block:
{ |_,o,n| o+n }
to determine values of keys that are present in both hashes being merged. The key is the intestate (written _
because it is not be used), o
is an array of cities for the key in the hash h
being constructed and n
is an array of a single city for the merging hash { i=>[k] }
.
Upvotes: 0
Reputation: 29328
Given the assumption that @wurde's Hash structure is true I would do something like this
cities = {
'Bakersfield' => {
state: 'California',
interstate: ['I-5']
},
'Oakland' => {
state: 'California',
interstate: ['I-80']
},
'Atlanta' => {
state: 'Georgia',
interstate: ["I-20", "I-75", "I-86"]
},
'Cleveland' => {
state: 'Ohio',
interstate: ["I-71", "I-77", "I-80", "I-90"]
},
'Arlington' => {
state: 'Texas',
interstate: ["I-20", "I-30"]
}
}
cities.each_with_object(Hash.new {|h,k| h[k] = []}) do |(city_name,data),h|
data[:interstate].each do |interstate|
h[interstate] << "#{city_name}, #{data[:state]}"
end
end
#=> {"I-5"=>["Bakersfield, California"],
"I-80"=>["Oakland, California", "Cleveland, Ohio"],
"I-20"=>["Atlanta, Georgia", "Arlington, Texas"],
"I-75"=>["Atlanta, Georgia"],
"I-86"=>["Atlanta, Georgia"],
"I-71"=>["Cleveland, Ohio"],
"I-77"=>["Cleveland, Ohio"],
"I-90"=>["Cleveland, Ohio"],
"I-30"=>["Arlington, Texas"]}
Upvotes: 1
Reputation: 2617
cities = {
'Bakersfield' => {
state: 'California',
interstate: ['I-5']
},
'Oakland' => {
state: 'California',
interstate: ['I-80']
},
'Atlanta' => {
state: 'Georgia',
interstate: ["I-20", "I-75", "I-86"]
},
'Cleveland' => {
state: 'Ohio',
interstate: ["I-71", "I-77", "I-80", "I-90"]
},
'Arlington' => {
state: 'Texas',
interstate: ["I-20", "I-30"]
}
}
interstates = {}
cities.each do |city|
city[1][:interstate].each do |road|
interstates[road] ||= []
interstates[road] << city[0]
end
end
puts interstates.inspect
#=> {
#=> "I-5"=>["Bakersfield"],
#=> "I-80"=>["Oakland", "Cleveland"],
#=> "I-20"=>["Atlanta", "Arlington"],
#=> "I-75"=>["Atlanta"],
#=> "I-86"=>["Atlanta"],
#=> "I-71"=>["Cleveland"],
#=> "I-77"=>["Cleveland"],
#=> "I-90"=>["Cleveland"],
#=> "I-30"=>["Arlington"]
#=> }
Upvotes: 0
Reputation: 4515
try this
mapping = {}
cities.each do |city|
city.interstates.each do |interstate|
mapping[interstate] ||= []
mapping[interstate] << city
end
end
Upvotes: 1