重构:循环从嵌套数组中检索元素

Refactoring: Round-robin retrieve elements from nested arrays

我需要检查一个散列数组,每个散列包含一个标签和一个数据数组。最终结果将是一个串联的字符串,首先是标签,然后是与该标签对应的数据。

输入的哈希数组如下所示:
[{label: "first", data: [1, 2]}, {label: "second", data: [3, 4, 5]}, {label: "third", data: []}, {label: "fourth", data: [6]}]
在这个例子中,max_returns 会像这样高:10

def round_robin(arr, max_returns)
  result = ''
  i = 0 # number of grabbed elements
  j = 0 # inner array position
  k = 0 # outer array position
  l = 0 # number of times inner array length has been exceeded
  while i < max_returns do
    if k >= arr.length
      j += 1
      k = 0
    end
    element = arr[k]
    if element[:data].empty?
      k += 1
      next
    end

    if j >= element[:data].length
      l += 1
      k += 1

      if l > arr.length && i < max_returns
        break
      end
      next
    end
    result += element[:label] + ': ' + element[:data][j].to_s + ', '
    i += 1
    k += 1
  end
  result
end

根据上面给出的输入,输出应该是:
"first: 1, second: 3, fourth: 6, first: 2, second: 4, second: 5"

另外:max_returns 是检索结果总数的最大数量。因此,如果我的示例有一个 max_returns = 3,那么输出将是:
"first: 1, second: 3, fourth: 6"

问题:是否有更好或更有效的方法以循环方式从多个数组中获取数据?

我不确定什么是循环法,但这是提供您需要的输出的解决方案:

基于初始数组元素删除的版本:

arr = [{label: "first", data: [1, 2]}, {label: "second", data: [3, 4, 5]}, {label: "third", data: []}, {label: "fourth", data: [6]}]

loop do
  arr.each do |hash|                               # go through each hash
    num = hash[:data].delete_at(0)                 # remove first element in data array
    puts "#{hash[:label]}: #{num}" unless num.nil? # output it if it's not nil(means array was empty)
  end
  break if arr.map { |i| i[:data] }.flatten == []  # stop if all arrays are empty
end

不改变初始数组的版本:

arr = [{label: "first", data: [1, 2]}, {label: "second", data: [3, 4, 5]}, {label: "third", data: []}, {label: "fourth", data: [6]}]

max_data_size = arr.map { |i| i[:data] }.map(&:size).max
loop.with_index do |_, i|
  arr.each do |hash|
    num = hash[:data][i]
    puts "#{hash[:label]}: #{num}" unless num.nil?
  end
  break if i >= max_data_size - 1
end
▶ input = [{label: "first", data: [1, 2]},
           {label: "second", data: [3, 4, 5]},
           {label: "third", data: []},
           {label: "fourth", data: [6]}]

▶ max = input.max_by { |e| e[:data].size }[:data].size

▶ input.map do |h|
    [[h[:label]] * max].flatten.zip h[:data] # make it N×M (for transpose)
  end.transpose.map do |e|
    e.reject { |_, v| v.nil? }               # remove nils
  end.flatten(1).map { |e| e.join ': ' }.join ', '

#⇒  "first: 1, second: 3, fourth: 6, first: 2, second: 4, second: 5"

如果没有最后两个连接,结果将是一个数组数组:

#⇒ [["first", 1], ["second", 3], ["fourth", 6], 
#   ["first", 2], ["second", 4], ["second", 5]]
arr = [{ label: "first",  data: [1, 2] },
       { label: "second", data: [3, 4, 5] },
       { label: "third",  data: [] },
       { label: "fourth", data: [6] }]

labels, data = arr.map { |h| [h[:label], h[:data].dup] }.transpose
  #=> [["first", "second", "third", "fourth"], [[1, 2], [3, 4, 5], [], [6]]] 
data.map(&:size).max.times.with_object([]) do |_,arr|
  labels.each_index do |i|
    d = data[i].shift
    arr << "#{labels[i]}: #{d}" if d
  end
end.join(', ')
  #=> "first: 1, second: 3, fourth: 6, first: 2, second: 4, second: 5"  

基准都是 运行 针对相同的数据。我 运行 每个答案针对四种不同的场景:
*_5 与原始数据相比 运行:852, 0, 0, 0
*_500 是 运行 针对相同的数据,但最多 500 returns。
*_2_5 是 运行 针对 4 个数组中的数据,大小分别为:656、137、0、59,总共 852 条记录。
*_2_500 是 运行 对抗 arr2,最大 return 为 500。

                       user     system      total        real
OP_5:              0.000000   0.000000   0.000000 (  0.000120)
Mudasobwa_5:       0.000000   0.000000   0.000000 (  0.000108)
Cary_5:            0.010000   0.000000   0.010000 (  0.011316)
Rustam_5:          0.000000   0.000000   0.000000 (  0.000087)
Wand_5:            0.010000   0.000000   0.010000 (  0.003761)
Stefan_5:          0.000000   0.000000   0.000000 (  0.004007)
OP_500:            0.010000   0.010000   0.020000 (  0.017235)
Mudasobwa_500:     0.010000   0.000000   0.010000 (  0.006164)
Cary_500:          0.010000   0.000000   0.010000 (  0.011403)
Rustam_500:        0.010000   0.000000   0.010000 (  0.011884)
Wand_500:          0.010000   0.000000   0.010000 (  0.003743)
Stefan_500:        0.000000   0.000000   0.000000 (  0.002711)
OP_2_5:            0.000000   0.000000   0.000000 (  0.000052)
Mudasobwa_2_5:     0.000000   0.000000   0.000000 (  0.000140)
Cary_2_5:          0.010000   0.000000   0.010000 (  0.008196)
Rustam_2_5:        0.000000   0.000000   0.000000 (  0.000088)
Wand_2_5:          0.000000   0.000000   0.000000 (  0.003338)
Stefan_2_5:        0.010000   0.000000   0.010000 (  0.002597)
OP_2_500:          0.000000   0.000000   0.000000 (  0.002211)
Mudasobwa_2_500:   0.000000   0.000000   0.000000 (  0.006373)
Cary_2_500:        0.010000   0.000000   0.010000 (  0.008455)
Rustam_2_500:      0.020000   0.000000   0.020000 (  0.019453)
Wand_2_500:        0.010000   0.000000   0.010000 (  0.004846)
Stefan_2_500:      0.000000   0.000000   0.000000 (  0.003421)
OP_avg:            0.002500   0.002500   0.005000 (  0.004904)
Mudasobwa_avg:     0.002500   0.000000   0.002500 (  0.003196)
Cary_avg:          0.010000   0.000000   0.010000 (  0.009843)
Rustam_avg:        0.007500   0.000000   0.007500 (  0.007878)
Wand_avg:          0.007500   0.000000   0.007500 (  0.003922)
Stefan_avg:        0.002500   0.000000   0.002500 (  0.003184)

与我之前的基准相反,平均值表明 Stefan 的答案实际上是最快的,比 Mudasobwa 的答案快 0.000012 秒!

注意:我不得不编辑一些答案来模仿我原来的解决方案试图做的事情,所以在基准代码中添加了一些额外的东西目的。
此外,一些解决方案没有使用 max_returns 限制(或没有在限制处停止),这导致它们比其他解决方案花费的时间更长(当我最初问这个问题时,我责怪自己的 less-than-stellar 解释).我在选择答案时没有考虑 max_returns 限制,因为唯一符合它的解决方案是我的和 Wand 的(详见要点)。

可在此处找到执行这些基准测试的代码和示例数据:https://gist.github.com/scytherswings/65644610e20037bb948c

谢谢大家的回答!

这可行:

arr = [{ label: "first",  data: [1, 3] },
       { label: "second", data: [3, 4, 5] },
       { label: "third",  data: [] },
       { label: "fourth", data: [6] }]

results = []
arr.each do |h|
  h[:data].each_with_index do |d, i|
    results[i] ||= []
    results[i] << "#{h[:label]}: #{d}"
  end
end

results.flatten.join(', ')
#=> "first: 1, second: 3, fourth: 6, first: 3, second: 4, second: 5"