lastrad17
lastrad17

Reputation: 11

Flatten Hash with Values of Arrays By Order of Index in Ruby

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

Answers (4)

Cary Swoveland
Cary Swoveland

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

spickermann
spickermann

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

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

Les Nightingill
Les Nightingill

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

Related Questions