【问题标题】:Refactoring: Round-robin retrieve elements from nested arrays重构:循环检索嵌套数组中的元素
【发布时间】:2016-04-09 13:16:38
【问题描述】:

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

输入的哈希数组如下所示:
[{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"

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

【问题讨论】:

  • 首先应该是second: 3 吗? {label: "second", data: ["3", "4", "5"]}
  • 您是对的,感谢您的发现!我已经修改了我的示例以匹配。
  • round-robin 不是有效的方法名称,在将element[:data][j] 附加到result 字符串时似乎有TypeError
  • @Stefan 你说得对,我仓促地创建了方法名称和示例数据,并没有足够彻底地检查它。我已经纠正了我的例子。

标签: arrays ruby performance refactoring


【解决方案1】:

基准测试都是针对相同的数据运行的。我针对四种不同的场景运行了每个答案:
*_5 针对原始数据运行:852、0、0、0
*_500 针对相同数据运行,但最多返回 500 个。*_2_5 针对 4 个数组中的数据运行,大小分别为:656、137、0、59,总共 852 条记录。
*_2_500 针对 arr2 运行,最大回报为 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 限制(或没有停止在限制处),这导致它们比其他解决方案花费更长的时间(当我最初问这个问题时,我责怪自己的解释不够出色)。我在选择答案时没有考虑max_returns 的限制,因为遵守它的唯一解决方案是我和 Wand 的(详见要点)。

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

谢谢大家的回答!

【讨论】:

  • 感谢基准测试(尽管我排在最后)。当您报告基准时,最好包含基准代码(包括数据或生成数据的方法)。这样,读者可以在修改他们的方法后运行基准测试。您运行了@Rustam 的两种方法中的哪一种?为了使其与其他方法具有可比性,您应该使用第二种方法,它不会改变数组。
  • @CarySwoveland 我已经用更好的数据和 Stefan 的答案更新了基准测试。感谢您的鼓励,我从这个问题中学到了很多东西,我也玩得很开心。干杯!
  • @CarySwoveland 哦,为了回答你之前的问题,我确实使用了 Rustam 答案的第二个版本;)
【解决方案2】:

这可行:

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"

【讨论】:

  • 迄今为止最好的答案,imo。读者:Stefan 本来可以把它写成单行的,但选择不这样做,大概是为了更容易理解。压缩后的形式为:arr.each_with_object([]) { |h,results| h[:data].each_with_index { |d, i| (results[i] ||= []) &lt;&lt; "#{h[:label]}: #{d}" } }.flatten.join(', ')。 Stefan,我想你有一个很好的借口可以在假期缺席。
【解决方案3】:
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"  

【讨论】:

    【解决方案4】:

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

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

    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
    

    【讨论】:

    • 我在基准测试中使用了您的第二个版本。当我更改数据以更好地适应问题时,您的解决方案运行速度比我第一次运行基准测试时慢一些,所以我将答案更改为@stefan's。不过感谢您的回答!
    【解决方案5】:
    ▶ 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]]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多