1

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']

4 Answers 4

2

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
Sign up to request clarification or add additional context in comments.

1 Comment

Both work great! Just want to follow up that the second option is more efficient according to my benchmarks.
1
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

Comments

-1

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.

Comments

-1
def interleave_hash_values(hash)
  arrays = hash.values
  max_length = arrays.map(&:length).max
  result = []

  (0...max_length).each do |i|
    arrays.each do |array|
      result << array[i] if i < array.length
    end
  end

  result
end

hash1 = { a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9] }
interleave_hash_values(hash1)
# => [1, 4, 6, 2, 5, 7, 3, 8, 9]

hash2 = { i: ['A'], ii: ['B', 'C', 'D'], iii: ['E'], iv: ['F', 'G'] }
interleave_hash_values(hash2)
# => ["A", "B", "E", "F", "C", "G", "D"]

1 Comment

This doesn't return the values in the order the OP expects them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.