Reputation: 11
I'm trying to find a way to flatten a hash of arrays, such that the order of the elements are in order of index. So 1st element of every array gets picked up, then second, then third, and so on, and if there is no element of that index then move on to the next array instead of returning nil or something.
Example 1: {a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9]}
Result: [1, 4, 6, 2, 5, 7, 3, 8, 9]
Example 2: {i: ['A'], ii: ['B', 'C', 'D'], iii: ['E'], iv: ['F', 'G]}
Result: ['A', 'B', 'E', 'F', 'C', 'G', 'D']
Upvotes: 1
Views: 308
Reputation: 110725
def extract_values_ordered_by_index(hash)
vals = hash.values.map(&:dup)
finish = [[]] * vals.size
loop.with_object([]) do |_,a|
break a if vals == finish
vals.each { |v| a << v.shift unless v.empty? }
end
end
doit({ a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9], d:[] })
#=> [1, 4, 6, 2, 5, 7, 3, 8, 9]
doit({i: ['A'], ii: ['B', 'C', 'D'], iii: ['E'], iv: ['F', 'G']})
#=> ["A", "B", "E", "F", "C", "G", "D"]
I first extract the hash's values to an array vals
, removing any empty arrays in the process.
I then build an array a
until vals
is empty, at which time I break the loop, returning a
.
At each iteration I shift the first element of each element of vals
(an array, guaranteed to be non-empty) and append the shifted element of a
, then remove all (now-empty) elements (arrays) of vals
.
I do it this way in part to avoid using Array#compact
, which I regard as an ugly--though admittedly useful--method.
If, however, compact
is to be used, one could write
def extract_values_ordered_by_index(hash)
vals = hash.values
vals.map { |a| Array.new(vals.max_by(&:size).size) { |i| a[i] } }
.transpose
.flatten
.compact
end
Upvotes: 1
Reputation: 106972
I suggest:
def extract_values_ordered_by_index(hash)
Array.new(hash.values.map(&:length).max) do |i|
hash.values.map { |array| array[i] }
end.flatten.compact
end
hash = {a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9]}
extract_values_ordered_by_index(hash)
#=> [1, 4, 6, 2, 5, 7, 3, 8, 9]
hash = {i: ['A'], ii: ['B', 'C', 'D'], iii: ['E'], iv: ['F', 'G']}
extract_values_ordered_by_index(hash)
#=> ["A", "B", "E", "F", "C", "G", "D"]
Another option is:
Array.new(hash.values.map(&:length).max)
.zip(*hash.values).flatten.compact
Upvotes: 2
Reputation: 1
Here's Other:
arr = {a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9]}
arr.values.reduce(:+)
# => [1,2,3,4,5,6,7,8,9]
Upvotes: -1
Reputation: 6156
Here's one way:
arr = {a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9]}
arr.values.
map{|ar| ar+Array.new(100)}. # pad with nils
inject{|a,ar| a.zip(ar)}.
flatten.
compact #=> [1, 4, 6, 2, 5, 7, 3, 8, 9]
it's pretty ugly though! In order to avoid the discarding of the elements of long arrays, pad them all with a lot of nils, and then discard the nils at the end with compact
. You have to choose a padding length (I chose 100) that's "long enough" that elements are not discarded in the zip.
Upvotes: -1