Flatten A Hash Of Arrays Into A Flat Array Of Hashes In Ruby
Grouped data — { "ruby" => [post1, post2], "rails" => [post3] } — often needs to be turned back into a flat list where each item carries its group. It is the inverse of group_by, and flat_map does it in one line.
Description
flat_map maps each key-value pair to an array and concatenates the results one level deep, which is exactly the shape needed here.
Inside the block, map each element of the value array into a hash that includes the key. Using merge rather than mutation keeps the source data untouched.
For the general case where values may or may not be arrays, wrap with Array() so a bare value is handled the same as a one-element array.
Sample input:
grouped = {
"ruby" => [{ title: "Pattern matching" }, { title: "Ractor" }],
"rails" => [{ title: "Turbo Streams" }]
}
Sample Output:
[{ category: "ruby", title: "Pattern matching" },
{ category: "ruby", title: "Ractor" },
{ category: "rails", title: "Turbo Streams" }]
Answer
grouped.flat_map do |category, items|
items.map { |item| item.merge(category: category) }
end
# => [{ title: "Pattern matching", category: "ruby" }, ...]
# Tolerate non-array values
grouped.flat_map do |category, items|
Array(items).map { |item| item.merge(category: category) }
end
# Values are scalars rather than hashes
{ "ruby" => %w[a b], "rails" => %w[c] }.flat_map do |cat, tags|
tags.map { |tag| { category: cat, tag: tag } }
end
# => [{category: "ruby", tag: "a"}, {category: "ruby", tag: "b"},
# {category: "rails", tag: "c"}]
# The inverse — flat list back into a grouped hash
flat = [{ category: "ruby", title: "A" }, { category: "rails", title: "B" }]
flat.group_by { |h| h[:category] }
.transform_values { |items| items.map { |i| i.except(:category) } }
# Just the pairs, no wrapping hash
grouped.flat_map { |cat, items| items.map { |i| [cat, i[:title]] } }
Check viewARU - Brand Newsletter!
Newsletter to DEVs by DEVs - boost your Personal Brand & career! 🚀