【问题标题】:Most efficient way to select elements in 2d array in Ruby在 Ruby 中选择二维数组中元素的最有效方法
【发布时间】:2021-03-22 14:21:44
【问题描述】:

假设我有二维数组(或矩阵),

matrix = [
 [1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]
]

而且,我必须选择某个元素matrix[i][j]的八个方向上的所有元素,所以我知道,

  • 上下元素将具有相同的j
  • 左右元素将具有相同的i
  • 后倾对角线 (\) 元素将具有相同的差异 i-j
  • 前倾斜对角线 (/) 元素的总和将相同 i+j

如何轻松高效地选择这些元素?有什么直接的方法吗?


例如,如果我的元素是4,那么我的选择应该产生[1, 7, 5, 6, 2, 8]。如果是,5,那么选择应该是all except 5


编辑:我已经编写了任何解决方案,因为我认为它会很差,但这是我的想法。 Try it online!

matrix = [
 [1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]
]

res = []
i = 0
j = 1

# let [i,j] be index of my element
matrix.each_with_index{|row,r| row.each_with_index{|col,c| 
    res << col if c == j || r == i || r+c == i+j || r-c == i-j 
}}

p res

【问题讨论】:

  • 我建议您先尝试,因为这样做是为了帮助您而不是提供难题解决方案
  • @ray - 我已经找到要检查的条件,我可以很差地使用select,但这不会高效且易于实施
  • @iGian - 你觉得哪个元素是错误的/不包括在内?
  • @vrintle 还假设二维矩阵不是对称的,您提供的输入为[1,0] 而不是4 这将是一个更明智的问题。
  • 数组是任意大小的吗?是否应该按特定顺序生成元素?最后同样重要的是:既然您要求“最有效”,我假设您已经有一个低效的解决方案/尝试 - 请展示它。

标签: arrays ruby select matrix direction


【解决方案1】:

m 是您从用户那里获取的矩阵,它是对称或非对称二维数组。

# inputs
m = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]
p, q = 1, 0 # co-ordinate of number 4

# code for output
i, j, result = m.size, m[0].size, []

i.times { |r| result.push(m[r][q]) if r != p }
j.times { |c| result.push(m[p][c]) if c != q }
i.times do |r|
    j.times do |c|
        result.push(m[r][c]) if [p,q] != [r,c] && (p-r).abs == (q-c).abs
    end
end
> result
 => [1, 7, 5, 6, 2, 8]

注意:如果不需要保留输出的顺序,我们可以只用2个循环来管理

【讨论】:

  • 关于效率:虽然只有2个循环,但这总是遍历每个元素。
【解决方案2】:

这里是示例代码,您可以进一步修改/简化它

class Matrix
  attr_reader :arr, :ele

  def initialize(arr, ele)
    @arr = arr
    @ele = ele
  end

  def find_elements
    position = arr.flatten.index(ele)
    len = arr.length
    x = position % len
    y = position / len
    horizontal_elements(y) + vertical_elements(x, y, len) + remaining_elements(x, y, len)
  end

  def horizontal_elements y
    arr[y] - [ele]
  end

  def vertical_elements x, y, len
    rows = (0..(len-1)).to_a - [y]
    rows.map{|row| arr[row][x] }
  end

  def remaining_elements(x, y, len)
    rows = []
    rows << y + 1 if y + 1 < len
    rows << y - 1 if y - 1 >= 0
    c_rows = []
    c_rows << x + 1 if x + 1 < len
    c_rows << x - 1 if x - 1 >= 0
    rows.map{|row| c_rows.map{|c_row| arr[row][c_row] } }.flatten
  end
end

matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
]
ele = 4
(1..9).each do |i|
  puts '*' * 20
  puts "For #{i}"
  puts Matrix.new(matrix, i).find_elements.inspect
  puts '*' * 20
end

O/P 如下所示

Desktop $ ruby something.rb
********************
For 1
[2, 3, 4, 7, 5]
********************
********************
For 2
[1, 3, 5, 8, 6, 4]
********************
********************
For 3
[1, 2, 6, 9, 5]
********************
********************
For 4
[5, 6, 1, 7, 8, 2]
********************
********************
For 5
[4, 6, 2, 8, 9, 7, 3, 1]
********************
********************
For 6
[4, 5, 3, 9, 8, 2]
********************
********************
For 7
[8, 9, 1, 4, 5]
********************
********************
For 8
[7, 9, 2, 5, 6, 4]
********************
********************
For 9
[7, 8, 3, 6, 5]
********************

【讨论】:

    【解决方案3】:

    代码

    def extract(matrix, row, col)
      last_row = matrix.size-1
      last_col = matrix.first.size-1
      diag_sum = row + col
      diag_first_row = diag_sum - [diag_sum, last_col].min
      diag_last_row  = [last_row, diag_sum].min
      ante_diag_diff = row - col
      ante_diag_first_row = [ante_diag_diff, 0].max
      ante_diag_last_row = ante_diag_diff +
        [last_col, last_row - ante_diag_diff].min
      arr = [] 
      (0..last_col).each { |j| arr << matrix[row][j] unless j == col }
      (0..last_row).each { |i| arr << matrix[i][col] unless i == row }
      (diag_first_row..diag_last_row).each do |i|
        arr << matrix[i][diag_sum - i] unless i == row
      end
      (ante_diag_first_row..ante_diag_last_row).each do |i|
        arr << matrix[i][i - ante_diag_diff] unless i == row
      end
      arr
    end
    

    示例

    matrix = Array.new(5) { Array.new(5) { rand(10..99) } }
      #=> [[52, 29, 61, 35, 27],
      #    [68, 99, 67, 18, 67],
      #    [79, 10, 73, 15, 36],
      #    [49, 94, 28, 24, 53],
      #    [37, 26, 65, 65, 43]]
    
    (0..4).each do |i|
      (0..4).each do |j|
        puts "#{i}, #{j}: #{extract(matrix,i,j)}"
      end
     end
    
    i  j
    ----------------------------------------------------------------------
    0, 0: [29, 61, 35, 27, 68, 79, 49, 37, 99, 73, 24, 43]
    0, 1: [52, 61, 35, 27, 99, 10, 94, 26, 68, 67, 15, 53]
    0, 2: [52, 29, 35, 27, 67, 73, 28, 65, 99, 79, 18, 36]
    0, 3: [52, 29, 61, 27, 18, 15, 24, 65, 67, 10, 49, 67]
    0, 4: [52, 29, 61, 35, 67, 36, 53, 43, 18, 73, 94, 37]
    
    1, 0: [99, 67, 18, 67, 52, 79, 49, 37, 29, 10, 28, 65]
    1, 1: [68, 67, 18, 67, 29, 10, 94, 26, 61, 79, 52, 73, 24, 43]
    1, 2: [68, 99, 18, 67, 61, 73, 28, 65, 35, 10, 49, 29, 15, 53]
    1, 3: [68, 99, 67, 67, 35, 15, 24, 65, 27, 73, 94, 37, 61, 36]
    1, 4: [68, 99, 67, 18, 27, 36, 53, 43, 15, 28, 26, 35]
    
    2, 0: [10, 73, 15, 36, 52, 68, 49, 37, 61, 99, 94, 65]
    2, 1: [79, 73, 15, 36, 29, 99, 94, 26, 35, 67, 49, 68, 28, 65]
    2, 2: [79, 10, 15, 36, 61, 67, 28, 65, 27, 18, 94, 37, 52, 99, 24, 43]
    2, 3: [79, 10, 73, 36, 35, 18, 24, 65, 67, 28, 26, 29, 67, 53]
    2, 4: [79, 10, 73, 15, 27, 67, 53, 43, 24, 65, 61, 18]
    
    3, 0: [94, 28, 24, 53, 52, 68, 79, 37, 35, 67, 10, 26]
    3, 1: [49, 28, 24, 53, 29, 99, 10, 26, 27, 18, 73, 37, 79, 65]
    3, 2: [49, 94, 24, 53, 61, 67, 73, 65, 67, 15, 26, 68, 10, 65]
    3, 3: [49, 94, 28, 53, 35, 18, 15, 65, 36, 65, 52, 99, 73, 43]
    3, 4: [49, 94, 28, 24, 27, 67, 36, 43, 65, 29, 67, 15]
    
    4, 0: [26, 65, 65, 43, 52, 68, 79, 49, 27, 18, 73, 94]
    4, 1: [37, 65, 65, 43, 29, 99, 10, 94, 67, 15, 28, 49]
    4, 2: [37, 26, 65, 43, 61, 67, 73, 28, 36, 24, 79, 94]
    4, 3: [37, 26, 65, 43, 35, 18, 15, 24, 53, 68, 10, 28]
    4, 4: [37, 26, 65, 65, 27, 67, 36, 53, 52, 99, 73, 24]
    

    说明

    首先观察,如果目标元素在行i和列j中,则通过该点的对角线上的元素[p,q]具有p+q == i+j的属性。类似地,通过该点的前对角线上的元素[p,q] 具有p-q == i-j 的属性

    假设

    row = 1
    col = 2
    

    然后

    last_row = matrix.size-1
      #=> 4 
    last_col = matrix.first.size-1
      #=> 4 
    
    diag_sum = row + col
      #=> 3 
    diag_first_row = diag_sum - [diag_sum, last_col].min
      #=> 0 
    diag_last_row  = [last_row, diag_sum].min
      #=> 3 
    
    ante_diag_diff = row - col
      #=> -1 
    ante_diag_first_row = [ante_diag_diff, 0].max
      #=> 0 
    ante_diag_last_row = ante_diag_diff +
      [last_col, last_row - ante_diag_diff].min
      #=> 3 
    

    剩下的计算很简单。


    这是上面的一个变体,它使用的代码更少,效率可能略低。

    def extract(matrix, row, col)
      row_range = 0..matrix.size-1
      col_range = 0..matrix.first.size-1
      diag_sum = row + col
      ante_diag_diff = row - col
      arr = [] 
      col_range.each { |j| arr << matrix[row][j] unless j == col }
      row_range.each do |i|
         next if i == row
         arr << matrix[i][col]
         j = diag_sum - i
         arr << matrix[i][j] if col_range.cover?(j)
         j = i - ante_diag_diff
         arr << matrix[i][j] if col_range.cover?(j)         
      end
      arr
    end
    

    【讨论】:

      【解决方案4】:

      递归方式

      我想提出一种不同的方法,没有测试它是否或多或少的效率,但可以提供一些对订单的控制等等。

      让我们从这个矩阵开始,其中元素告诉它们的索引:

      mat = [
        ['00', '01', '02'],
        ['10', '11', '12'],
        ['20', '21', '22'],
        ['30', '31', '32']
      ]
      

      并定义一个辅助方法来获取矩阵的形状并检查哪个格式正确:

      def shape(mat)
        raise 'Out of shape' if mat.map(&:size).uniq.size > 1
        return mat.size, mat[0].size
      end
      

      实施

      现在,让我们定义一个方法,将起点、矩阵(形状)的边界和方向作为输入。

      def walk(i_0, j_0, i_max, j_max, direction, res=[])
        d_i, d_j = direction
        next_step = [i_0 + d_i, j_0 + d_j]
        if [-1, i_max].include?(next_step[0]) || [-1, j_max].include?(next_step[1])
          return res
        end
        res << next_step
        i_0, j_0 = next_step
        walk(i_0, j_0, i_max, j_max, direction, res)
      end
      

      该方法返回从原点开始沿着方向行走的索引列表。

      使用示例

      示例 1

      i_0, j_0 = [2, 0]
      i_max, j_max = shape(mat)
      direction = [-1, 1]
      walk(i_0, j_0, *shape(mat), direction)
      #=> [[1, 1], [0, 2]]
      

      示例 2

      您可以使用它来提取给定路线列表的索引。

      可以安排方向列表以给出扫描顺序(顺时针、顺时针)或只是跳过一些方向,如您所愿。

      directions = [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]]
      
      i_0, j_0 = [2, 0]
      directions = [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1]]
      directions.flat_map { |direction| walk(i_0, j_0, *shape(mat), direction) }
      #=> [[3, 0], [1, 0], [0, 0], [2, 1], [2, 2], [3, 1], [1, 1], [0, 2]]
      

      示例 3

      (这是示例 2 的扩展) 可以直接从矩阵中获取元素:

      res = directions.flat_map do |direction|
        walk(i_0, j_0, *shape(mat), direction).map do |coords|
          i, j = coords
          mat[i][j]
        end
      end
      
      res
      #=> ["30", "10", "00", "21", "22", "31", "11", "02"]
      

      可以使用更长的步骤(例如direction = [2, 2])升级walk 方法。

      【讨论】:

        猜你喜欢
        • 2021-03-23
        • 2020-09-28
        • 2016-01-16
        • 1970-01-01
        • 2016-09-12
        • 1970-01-01
        • 2018-08-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多