【问题标题】:How to optimise code that parses a 2-d array in Ruby如何优化在 Ruby 中解析二维数组的代码
【发布时间】:2020-06-09 12:48:42
【问题描述】:

注意:这个问题提出了一个我已经解决的问题,但是我觉得我的解决方案非常初级,其他人,比如我自己,会从更有经验的开发人员的讨论中受益.解决问题的不同方法以及更复杂的方法和算法将不胜感激。我觉得这是一个学习 Ruby 如何解决我认为对于初学者来说相当困难的问题的好地方。

给定一个 6x6 2D 数组arr

1 1 1 0 0 0
0 1 0 0 0 0
1 1 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0

我们将arr 中的沙漏定义为值的子集,在arr 的图形表示中,索引落在此模式中:

a b c
  d
e f g

arr 中有 16 个沙漏,沙漏和是沙漏值的总和。计算arr 中每个沙漏的沙漏总和,然后打印最大沙漏总和。

例如,给定二维数组:

arr = [
  [-9, -9, -9,  1, 1, 1], 
  [ 0, -9,  0,  4, 3, 2],
  [-9, -9, -9,  1, 2, 3],
  [ 0,  0,  8,  6, 6, 0],
  [ 0,  0,  0, -2, 0, 0],
  [ 0,  0,  1,  2, 4, 0]
]

我们计算以下沙漏值:

-63, -34, -9, 12, 
-10, 0, 28, 23, 
-27, -11, -2, 10, 
9, 17, 25, 18

我们最高的沙漏值来自沙漏:

0 4 3
  1
8 6 6

我的解决办法是:

def hourglass_sum(arr)
  hourglasses = []

  arr.each_with_index do |row, i|
    # rescue clause to prevent iterating outside the array
    unless arr[i].nil?

      arr[i].length.times do |iteration|
        # generate n 3x3 arrays
        r1 = arr[i][iteration...iteration+3]
        r2 = arr[i+1][iteration...iteration+3] if arr[i+1] != nil
        r3 = arr[i+2][iteration...iteration+3] if arr[i+2] != nil

        # rescue clause to stop creating 3x3 arrays that fall outside given input array
        if arr[i+1] != nil && arr[i+2] != nil
          # take all values except indices 0 and 5 from the 9 element array
          result = r1 + [r2[1]] + r3
          hourglasses << result.sum unless result.include? nil
        end
      end
    end
  end
  p hourglasses.max
end

arr = [[-9, -9, -9, 1, 1, 1], [0, -9,  0,  4, 3, 2], [-9, -9, -9, 1, 2, 3], [0, 0, 8, 6, 6, 0], [0, 0 ,0, -2, 0, 0], [0, 0, 1, 2, 4, 0]]

hourglass_sum(arr)
# => 28

【问题讨论】:

  • 感谢@CarySwoveland 的建议。我已经适当地编辑了这个问题。
  • 您不需要在末尾定义arr,因为它已经在前面定义了。 (一般情况下,尝试格式化以便读者不必水平滚动。)一种 Ruby 约定是使用 snake case 来命名 Ruby 变量和方法(例如,hourglass_sum 而不是 hourglassSum)。当然,您不必遵守该约定,但如果您不遵守约定,如果您看到一群愤怒的 Rubyists 拿着火把和干草叉朝您走来,请不要感到惊讶。
  • 感谢@CarySwoveland,根据您的建议编辑了代码。
  • “始终编写代码,就好像最终维护您的代码的人将是一个知道您住在哪里的暴力精神病患者。代码可读性。” — 约翰·伍兹
  • 我建议检查“Code Review”是否是一个更好的站点,或者在如何优化代码方面可能更具指导性。有关更多信息,请参阅其“On-topic”信息。

标签: arrays ruby algorithm multidimensional-array


【解决方案1】:

一种选择是使用Matrix 方法。

require 'matrix'

ma = Matrix[*arr]
  #=> Matrix[[-9, -9, -9,  1, 1, 1],
  #          [ 0, -9,  0,  4, 3, 2],
  #          [-9, -9, -9,  1, 2, 3],
  #          [ 0,  0,  8,  6, 6, 0],
  #          [ 0,  0,  0, -2, 0, 0],
  #          [ 0,  0,  1,  2, 4, 0]] 

mi = Matrix.build(6-3+1) { |i,j| [i,j] }
  #=> Matrix[[[0, 0], [0, 1], [0, 2], [0, 3]],
  #          [[1, 0], [1, 1], [1, 2], [1, 3]],
  #          [[2, 0], [2, 1], [2, 2], [2, 3]],
  #          [[3, 0], [3, 1], [3, 2], [3, 3]]]

def hourglass_val(r,c,ma)
  mm = ma.minor(r,3,c,3)
  mm.sum - mm[1,0] - mm[1,2]
end

max_hg = mi.max_by { |r,c| hourglass_val(r,c,ma) }
  #=> [1,2] 
hourglass_val(*max_hg,ma)
  #=> 28

[1,2]arr 中最优沙漏左上角的行和列索引。

【讨论】:

  • 读者:我希望Matrix.build(4,4,&amp;:itself) 能够工作,但它引发了异常#=&gt; "...matrix.rb:103:in 'itself'; ArgumentError (wrong number of arguments (given 1, expected 0))。谁能解释那个错误信息?
  • 您作为临时块传入的方法调用似乎正在传递一个参数,但方法“本身”不接受参数。基本上‘&:itself’等价于写‘{|x| x.itself}',但您的带有传入块的示例有两个参数。所以“&:”快捷方式在底层构建了错误的代码,你会得到一个参数错误。
  • 在大多数情况下,产生的元素作为单个数组参数传递,并在遇到逗号时通过数组分解(在hash.each { |key, value| })隐式分配。如果h = {a: 1}h.each { |e| p e } 将输出[:a, 1] 因此h.map(&amp;:itself) #=&gt; [[:a, 1]]。 (1/2)
  • Matrix::build 方法似乎不是这种情况。它似乎产生了两个单独的参数,而不是产生一个包含两个值的数组参数。 Matrix.build(1, 1) { |e| p e } 输出 0(不是 [0, 0])。因此,提供Matrix.build(1, 1, &amp;:itself) 将执行Matrix.build(1, 1) { |i, j| :itself.to_proc.call(i, j) },它等效于Matrix.build(1, 1) { |i, j| i.itself(j) }。由于Object#itself 不接受任何参数,因此您会收到遇到的消息。 (2/2)
  • ...@3limin4t0r。 3lim 的等价性特别明显。
【解决方案2】:

这是我想出的一个选项。

def width_height(matrix)
  [matrix.map(&:size).max || 0, matrix.size]
end

def sum_with_weight_matrix(number_matrix, weight_matrix)
  number_width, number_height = width_height(number_matrix)
  weight_width, weight_height = width_height(weight_matrix)

  width_diff  = number_width  - weight_width
  height_diff = number_height - weight_height

  0.upto(height_diff).map do |y|
    0.upto(width_diff).map do |x|
      weight_height.times.sum do |ry|
        weight_width.times.sum do |rx|
          weight = weight_matrix.dig(ry, rx) || 0
          number = number_matrix.dig(y + ry, x + rx) || 0
          number * weight
        end
      end
    end
  end
end

arr = [
  [-9, -9, -9,  1, 1, 1], 
  [ 0, -9,  0,  4, 3, 2],
  [-9, -9, -9,  1, 2, 3],
  [ 0,  0,  8,  6, 6, 0],
  [ 0,  0,  0, -2, 0, 0],
  [ 0,  0,  1,  2, 4, 0],
]

weights = [
  [1, 1, 1],
  [0, 1, 0],
  [1, 1, 1],
]

sum_matrix = sum_with_weight_matrix(arr, weights)
#=> [
#   [-63, -34, -9, 12],
#   [-10,   0, 28, 23],
#   [-27, -11, -2, 10],
#   [  9,  17, 25, 18]
# ]
max_sum = sum_matrix.flatten.max
#=> 28

此解决方案使用width_diffheight_diff 创建输出矩阵(样本数据0.upto(6 - 3).to_a #=&gt; [0, 1, 2, 3] 的4x4)。与较大的number_matrix 相比,weight_matrixrxry)的索引将用作相对索引。

如果您的二维数组的每个子数组始终具有相同数量的元素,您可以将matrix.map(&amp;:size).max 替换为matrix[0]&amp;.size || 0 以加快确定矩阵宽度。当前解决方案使用子数组的最大大小。具有较小尺寸的子数组将使用0 来表示缺失的元素,因此不会影响总和。

我的解决方案可能有点多变。我这样做是为了有描述性的变量名称,希望能告诉你最需要了解的解决方案。您可以缩短变量名称,或者在您觉得不需要它们时完全删除它们。

如果有不清楚的地方,请在 cmets 中询问。

【讨论】:

    【解决方案3】:

    在不使用 Matrix 类的情况下,以下是我对任意矩形数组的处理方式:

    offsets = [[-1, -1], [-1, 0], [-1, 1], [0, 0], [1, -1],  [1, 0],  [1, 1]]
    sums = 1.upto(arr.length - 2).flat_map do |i|
      1.upto(arr[0].length - 2).map do |j|
        offsets.map {|(x, y)| arr[i+x][j+y] }.sum
      end
    end
    
    puts sums.max
    

    我们感兴趣的值只是当前位置的偏移量。我们可以通过一些行列偏移量映射出数组中相对于当前位置的值,对它们求和,然后选择和的最大值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-08
      • 1970-01-01
      • 2015-08-27
      • 1970-01-01
      • 2020-01-14
      • 1970-01-01
      • 1970-01-01
      • 2012-12-05
      相关资源
      最近更新 更多