从Ruby

时间:2017-07-09 19:00:30

标签: ruby matrix iteration

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

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

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   

4 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:1)

这既短又快:

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

答案 2 :(得分:0)

解决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提到,算法仍然不是很好,我想要一些更好算法的建议。

答案 3 :(得分:0)

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