tags:

views:

57

answers:

2

If I want to interleave a set of arrays in Ruby, and each array was the same length, we could do so as:

a.zip(b).zip(c).flatten

However, how do we solve this problem if the arrays can be different sizes?

We could do something like:

def interleave(*args)
  raise 'No arrays to interleave' if args.empty?
  max_length = args.inject(0) { |length, elem| length = [length, elem.length].max }
  output = Array.new
  for i in 0...max_length
    args.each { |elem|
      output << elem[i] if i < elem.length
    }
  end
  return output
end

But is there a better 'Ruby' way, perhaps using zip or transpose or some such?

+2  A: 

Your implementation looks good to me. You could achieve this using #zip by filling the arrays with some garbage value, zip them, then flatten and remove the garbage. But that's too convoluted IMO. What you have here is clean and self explanatory, it just needs to be rubyfied.

Edit: Fixed the booboo.

def interleave(*args)
  raise 'No arrays to interleave' if args.empty?
  max_length = args.map(&:size).max
  output = []
  max_length.times do |i|
    args.each do |elem|
      output << elem[i] if i < elem.length
    end
  end
  output
end

a = [*1..5]
# => [1, 2, 3, 4, 5]
b = [*6..15]
# => [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
c = [*16..18]
# => [16, 17, 18]

interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]

Edit: For fun

def interleave(*args)
  raise 'No arrays to interleave' if args.empty?
  max_length = args.map(&:size).max
  # assumes no values coming in will contain nil. using dup because fill mutates
  args.map{|e| e.dup.fill(nil, e.size...max_length)}.inject(:zip).flatten.compact
end

interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]
thorncp
ChrisInEdmonton
And I had thought about padding the shorter arrays with nils, interleaving them, then compacting out the nils. That's great if and only if you can be sure your source arrays don't have any nils in them. :)
ChrisInEdmonton
+1  A: 

If the source arrays don't have nil in them, you only need to extend the first array with nils, zip will automatically pad the others with nil. This also means you get to use compact to clean the extra entries out which is hopefully more efficient than explicit loops

def interleave(a,*args)
    max_length = args.map(&:size).max
    padding = [nil]*[max_length-a.size, 0].max
    (a+padding).zip(*args).flatten.compact
end

Here is a slightly more complicated version that works if the arrays do contain nil

def interleave(*args)
    max_length = args.map(&:size).max
    pad = Object.new()
    args = args.map{|a| a.dup.fill(pad,(a.size...max_length))}
    ([pad]*max_length).zip(*args).flatten-[pad]
end
gnibbler