如何在Ruby中交错不同长度的数组

8
如果我想在Ruby中交错一组数组,并且每个数组的长度相同,我们可以这样做:
a.zip(b).zip(c).flatten

然而,如果数组的大小不同,我们该如何解决这个问题呢?我们可以尝试这样做:
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

但是,也许有更好的“Ruby”方式,例如使用zip或transpose等方法吗?

3个回答

8

这里有一个更简单的方法。它利用了您传递数组给 zip 的顺序:

def interleave(a, b)
  if a.length >= b.length
    a.zip(b)
  else
    b.zip(a).map(&:reverse)
  end.flatten.compact
end

interleave([21, 22], [31, 32, 33])
# => [21, 31, 22, 32, 33]

interleave([31, 32, 33], [21, 22])
# => [31, 21, 32, 22, 33]

interleave([], [21, 22])
# => [21, 22]

interleave([], [])
# => []

警告:这会移除所有的nil

interleave([11], [41, 42, 43, 44, nil])
# => [11, 41, 42, 43, 44]

7

如果源数组中没有nil,您只需要使用nil扩展第一个数组,zip将自动使用nil填充其他数组。这也意味着您可以使用compact清除多余的条目,这比显式循环更有效率。

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

这里是一个稍微复杂一些的版本,适用于数组中包含 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

5
你的实现看起来不错。你可以使用 #zip,通过填充一些垃圾值到数组中,然后压缩它们,再展平并移除垃圾值,从而实现这个目标。但我认为那太复杂了。你现在的代码很干净、易于理解,只需要做一些 Ruby 化处理即可。 编辑:修复了错误。
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]

编辑:为了娱乐

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]

谢谢,我没有考虑过 args.map(&:size)。事实上,我以前从未见过这种方法。max_length.times 比我的 for 循环更简洁。 - ChrisInEdmonton
我曾考虑过用nil填充较短的数组,然后交错它们,最后压缩掉nil。但前提是你能确定源数组中没有任何nil。 :) - ChrisInEdmonton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接