【问题标题】:Obtaining a list of 2x2 matrices from a large matrix in Ruby从 Ruby 中的大矩阵中获取 2x2 矩阵的列表
【发布时间】:2017-07-09 19:00:30
【问题描述】:

我一直在从事编码挑战,只是为了好玩,问题是这样的:
给定一个仅包含数字的矩形矩阵,计算其中不同的 2 × 2 正方形的数量。

2x2 矩阵可能重叠。输入最多可以是 100 x 100 矩阵,并且是矩形,但不一定是正方形。我能够使用嵌套循环解决这个问题,问题是对于大型矩阵的输入来说太慢了,并且超过了编码挑战的时间限制(4000 毫秒)。我最初就是这样解决的。

def differentSquares(matrix)
    i = 0
    squares = []
    while i < matrix.length - 1 
        j = 0
        while j < matrix[i].length - 1 
            current = [matrix[i][j], matrix[i][j+1], matrix[i+1][j], matrix[i+1][j+1]]
            squares << current if !squares.include?(current)
            j += 1 
        end 
        i += 1 
    end 
    squares.length 
end

我考虑过以某种方式使用哈希,因为它们的迭代速度比数组快得多,但我不知道该怎么做。谁能帮我找到一个比嵌套循环更快的实现?

输入和预期输出示例:

input:   
[[2,5,3,4,3,1,3,2],   
 [4,5,4,1,2,4,1,3],   
 [1,1,2,1,4,1,1,5],   
 [1,3,4,2,3,4,2,4],   
 [1,5,5,2,1,3,1,1],   
 [1,2,3,3,5,1,2,4],   
 [3,1,4,4,4,1,5,5],   
 [5,1,3,3,1,5,3,5],   
 [5,4,4,3,5,4,4,4]] 

expected output: 54  

another input:   
[[1,2,1],   
 [2,2,2],   
 [2,2,2],   
 [1,2,3],   
 [2,2,1]]

expected output: 6   

【问题讨论】:

  • 一些非常小的加速可能来自:使用 squares 的集合。然后只需添加“当前”而不检查(设置将确保唯一性)。您还可以确保只调用一次“长度”(因为矩阵是矩形的)。但这不会减少很多时间,我猜。所以你需要找到一个更好的算法......
  • 哇!我以前从未听说过 Set,但我试过了,它奏效了!谢谢帕斯卡,我真的很感激。我现在将更多地研究 Set 数据结构,感谢您向我介绍它。
  • 能否举个输入输出的例子?
  • @Stefan 我更新了问题以包含示例。
  • 我认为您对问题的描述可能需要一些改进,因为仅根据您的描述,我预计第二个示例的结果是 4,而不是 6。

标签: ruby matrix iteration


【解决方案1】:

以下是建议解决方案的集合,包括基准:

require 'benchmark/ips'
require 'set'
require 'matrix'

def generate(x, y, max)
  matrix = []
  x.times do
    row = []
    y.times do
      row << rand(max)
    end
    matrix << row
  end
  matrix
end

def original(matrix)
    i = 0
    squares = []
    while i < matrix.length - 1
        j = 0
        while j < matrix[i].length - 1
            current = [matrix[i][j], matrix[i][j+1], matrix[i+1][j], matrix[i+1][j+1]]
            squares << current if !squares.include?(current)
            j += 1
        end
        i += 1
    end
    squares.length
end

def with_set(matrix)
  i = 0
  squares = Set.new
  while i < matrix.length - 1
    j = 0
    while j < matrix[i].length - 1
      squares << [matrix[i][j], matrix[i][j+1], matrix[i+1][j], matrix[i+1][j+1]]
      j += 1
    end
    i += 1
  end
  squares.length
end

def with_set_and_length(matrix)
  i = 0
  squares = Set.new
  a = matrix.length - 1
  b = matrix.first.length - 1
  while i < a
    j = 0
    while j < b
      squares << [matrix[i][j], matrix[i][j+1], matrix[i+1][j], matrix[i+1][j+1]]
      j += 1
    end
    i += 1
  end
  squares.length
end

def foo(matrix)
  matrix.each_cons(2) do |row|
    row.each_cons(2) do |col|

    end
  end
end

def with_each_cons(m)
  m.each_cons(2).flat_map { |a, b| a.each_cons(2).zip(b.each_cons(2)) }.uniq.count
end

def with_each_cons_rearanged(m)
  m.map { |a| a.each_cons(2).to_a }.each_cons(2).flat_map { |a, b| a.zip(b) }.uniq.count
end

def with_matrix(m)
  (m.row_count-1).times.flat_map do |i|
    (m.column_count-1).times.map { |j| m.minor(i,2,j,2) }
  end.uniq.size
end


def with_matrix_and_set(m)
  set = Set.new
  (m.row_count-1).times do |i|
    (m.column_count-1).times do |j|
      set << m.minor(i, 2, j, 2)
    end
  end
  set.size
end

array_matrix = generate(100, 100, 20)
real_matrix = m = Matrix[*array_matrix]

Benchmark.ips do |x|
  x.compare!
  x.report('original') do
    original(array_matrix)
  end
  x.report('with_set') do
    with_set(array_matrix)
  end
  x.report('with_set_and_length') do
    with_set_and_length(array_matrix)
  end
  x.report('with_each_cons') do
    with_each_cons(array_matrix)
  end
  x.report('with_each_cons_rearanged') do
    with_each_cons_rearanged(array_matrix)
  end
  x.report('with_matrix') do
    with_matrix(real_matrix)
  end
  x.report('with_matrix_and_set') do
    with_matrix_and_set(real_matrix)
  end
end

结果:

Comparison:
 with_set_and_length:       52.7 i/s
            with_set:       52.0 i/s - 1.01x slower
with_each_cons_rearanged:       31.4 i/s - 1.68x slower
 with_matrix_and_set:       21.0 i/s - 2.52x slower
      with_each_cons:       18.7 i/s - 2.82x slower
         with_matrix:       17.5 i/s - 3.01x slower
            original:        0.1 i/s - 605.89x slower

【讨论】:

  • 如果您能将我的方法添加到您的基准测试中,我将不胜感激。
  • 更新了@CarySwoveland 的解决方案,该解决方案使用Matrix(以及使用Matrix + Set 的变体)。 @CarySwoveland:我将“矩阵”的创建移出基准测试,但它仍然比数组慢。我认为会有更多开销,因为“矩阵”在内部使用“数组”。
【解决方案2】:

这既短又快:

def different_squares(m)
  m.each_cons(2).flat_map { |a, b| a.each_cons(2).zip(b.each_cons(2)) }.uniq.count
end

它利用了each_conszip

重新排列方法使其更快:

def different_squares(m)
  m.map { |a| a.each_cons(2).to_a }.each_cons(2).flat_map { |a, b| a.zip(b) }.uniq.count
end

【讨论】:

  • 我也在考虑这样的事情。我认为你不能比 O(n^2) 做得更好,或者更准确地说是 O(n*m),但我很高兴被证明是错误的。 (或者,正确的。)
  • @JörgWMittag 可能不会,也许您可​​以以不同的方式解决整个问题。
  • @Stefan 哦,哇,这令人印象深刻。感谢您的答复。我一定会研究这个例子来学习。
【解决方案3】:

解决了 Pascal Betz 关于使用 Set 的建议:

require 'set'

def differentSquares(matrix)
    i = 0
    squares = Set.new 
    while i < matrix.length - 1 
        j = 0
        while j < matrix[i].length - 1 
            current = [matrix[i][j], matrix[i][j+1], matrix[i+1][j], matrix[i+1][j+1]]
            squares.add(current)
            j += 1 
        end 
        i += 1 
    end 
    squares.length 
end

虽然正如 Pascal 所提到的,该算法仍然不是很好,我希望有一些更好的算法的建议。

【讨论】:

  • 你原来的算法是O(n^4),或者更准确地说是O((n * m)^2),这个是O(n^2),或者更准确地说是O(n *米)。我很想看看是否有可能做得更好,但我现在想不出聪明的办法。
【解决方案4】:
require 'matrix'

def nbr_uniq_2x2_submatrices(arr)
  m = Matrix[*arr]
  (m.row_count-1).times.flat_map do |i|
    (m.column_count-1).times.map { |j| m.minor(i,2,j,2) }
  end.uniq.size
end

arr = [[2,5,3,4,3,1,3,2],
       [4,5,4,1,2,4,1,3],
       [1,1,2,1,4,1,1,5],
       [1,3,4,2,3,4,2,4],
       [1,5,5,2,1,3,1,1],
       [1,2,3,3,5,1,2,4],
       [3,1,4,4,4,1,5,5],
       [5,1,3,3,1,5,3,5],
       [5,4,4,3,5,4,4,4]]

nbr_uniq_2x2_submatrices(arr)
  #=> 54

Matrix 类的类和实例方法记录在 here 中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-02-08
    • 1970-01-01
    • 2014-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多