Reputation: 1307
In Ruby, how do I convert this:
{"1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"},
"2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"},
"3"=>{"id"=>3, "album"=>"album2", "track"=>"track1"},
"4"=>{"id"=>4, "album"=>"album2", "track"=>"track2"}}
into this:
{"album1"=>
{"1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"},
"2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"}},
"album2"=>
{"3"=>{"id"=>3, "album"=>"album2", "track"=>"track1"},
"4"=>{"id"=>4, "album"=>"album2", "track"=>"track2"}}}
in the most efficient way.
The first is the format that iTunes stores track information. The last is the format I'd need to process tracks at the level of 'album'. I've been staring at this all day and, not being very good at Ruby, have conceded defeat. Thank you for the tutorial on hash kung-foo.
EDIT
While I was waiting for the moderator to decide if this was OK, I got a solution:
album_tracks = {}
titles = []
tracks_hash.each do |album_id, album_hash|
titles << album_hash["album"] if !titles.include? album_hash["album"]
end
titles.each do |title|
tracks = {}
tracks_hash.each do |album_id, album_hash|
tracks[album_id] = album_hash if title == album_hash["album"]
end
albums_hash[title] = tracks
end
I'm guessing there is a more efficient strategy involving some sort of mapping that doesn't require passing over the entire hash twice?
Upvotes: 0
Views: 376
Reputation: 110725
One way is to use the form of Hash#update (aka merge!
) that employs a block to determine the values of keys that are present in both hashes being merged.
h = { "1"=>{ "id"=>1, "album"=>"album1", "track"=>"track1" },
"2"=>{ "id"=>2, "album"=>"album1", "track"=>"track2" },
"3"=>{ "id"=>3, "album"=>"album2", "track"=>"track1" },
"4"=>{ "id"=>4, "album"=>"album2", "track"=>"track2" } }
h.each_with_object({}) do |(k,v),g|
g.update(v["album"]=>{ k=>v}) { |_,o,n| o.update(n) }
end
#=> {"album1"=>{"1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"},
# "2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"}},
# "album2"=>{"3"=>{"id"=>3, "album"=>"album2", "track"=>"track1"},
# "4"=>{"id"=>4, "album"=>"album2", "track"=>"track2"}}}
Note that update
's argument,
v["album"]=>{ k=>v}
is shorthand for the hash
{ v["album"]=>{ k=>v} }
The steps:
enum = h.each_with_object({})
#=> #<Enumerator: {"1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"},
# "2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"},
# "3"=>{"id"=>3, "album"=>"album2", "track"=>"track1"},
# "4"=>{"id"=>4, "album"=>"album2", "track"=>"track2"}}:
# each_with_object({})>
The first element of enum
is passed to the block and the block variables are assigned using decomposition:
(k,v),g = enum.next
#=> [["1", {"id"=>1, "album"=>"album1", "track"=>"track1"}], {}]
k #=> "1"
v #=> {"id"=>1, "album"=>"album1", "track"=>"track1"}
g #=> {}
The block calculation is then performed:
g.update(v["album"]=>{ k=>v }) { |_,o,n| o.update(n) }
#=> {}.update("album1"=>{ "1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"})
# { |_,o,n| o.update(n) }
#=> {"album1"=>{ "1"=>{ "id"=>1, "album"=>"album1", "track"=>"track1"}}}
The two hashes being merged, {}
and
{ "album1"=>{ "1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"} }
have no keys in common, so the block is not used to for calculating any values.
The next element of enum
is then passed to the block:
(k,v),g = enum.next
#=> [["2", {"id"=>2, "album"=>"album1", "track"=>"track2"}], {}]
k #=> "2"
v #=> {"id"=>2, "album"=>"album1", "track"=>"track2"}
g #=> {"album1"=>{"1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"}}}
The update calculation is now
g.update(v["album"]=>{ k=>v }) { |_,o,n| o.update(n) }
#=> g.update("album1"=>{ "2"=> {"id"=>2, "album"=>"album1", "track"=>"track2"})
The hash we are building,
g #=> { "album1"=>{"1"=>{"id"=>1, "album"=>"album1", "track"=>"track1" } } }
and the hash being merged,
{ "album1"=>{ "2"=> {"id"=>2, "album"=>"album1", "track"=>"track2"} } }
both have the key "album1"
, so the block is employed for determining the value for that key:
In the block
{ |_,o,n| o.update(n) }
we have
o #=> { "1"=>{"id"=>1, "album"=>"album1", "track"=>"track1" } }
n #=> { "2"=>{"id"=>2, "album"=>"album1", "track"=>"track2" } }
so
{ "1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"} }.
update({ "2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"} })
#=> { "1"=>{"id"=>1, "album"=>"album1", "track"=>"track1" },
# "2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"} }
The hash g
is now
g #=> {"album1"=>{"1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"},
# "2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"}}}
The remaining calculations are similar.
Upvotes: 0
Reputation: 239382
Your output can be achieved through a pretty straight-forward call to group_by
, followed by a few transforms to turn the results back into hashes:
albums = {"1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"},
"2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"},
"3"=>{"id"=>3, "album"=>"album2", "track"=>"track1"},
"4"=>{"id"=>4, "album"=>"album2", "track"=>"track2"}}
albums.group_by { |k,v| v['album'] }.map { |k,v| [k, v.to_h] }.to_h
# => {
# "album1"=> {
# "1"=>{"id"=>1, "album"=>"album1", "track"=>"track1"},
# "2"=>{"id"=>2, "album"=>"album1", "track"=>"track2"}
# },
# "album2"=>{
# "3"=>{"id"=>3, "album"=>"album2", "track"=>"track1"},
# "4"=>{"id"=>4, "album"=>"album2", "track"=>"track2"}
# }
#}
The key is understanding which methods are available on Enumerable
for translating one structure into another (ie group_by
and map
) and then knowing that Ruby lets you freely transform arrays to hashes and vice versa.
The first, call, albums.group_by { |k,v| v['album'] }
, produces the correct outer Hash structure, but the values have the form [[key1, value1], [key2, value2], ...]
. Ruby will let you turn that same structure back into a {key1: value1, key2: value2}
hash using to_h
.
Upvotes: 2
Reputation: 2302
This should do the trick.
album_tracks = tracks_hash.each_with_object({}) do |(album_id, album_hash), album_tracks|
album_tracks[album_hash['album']] ||= {}
album_tracks[album_hash['album']][album_id] = album_hash
end
Upvotes: 0