NxM矩阵遍历

时间:2017-08-13 01:26:55

标签: ruby matrix traversal

我正在努力解决以下问题:

给定一个矩形矩阵,它只有两个可能的值'X'和'O'。 值'X'总是以矩形岛和这些岛的形式出现 总是按行和逐列分隔至少一行“O”。 计算给定矩阵中的岛数。

我已经生成了一个解决方案但是我的循环没有经过整个矩阵。我已经尝试在i循环中将j初始化为0,但是会引发错误。我是这个主题的新手,我真的想要了解为什么我的代码不起作用。

def matrix(input)
row = input.length
col = input[0].length
counter = 0
i = 0
j = 0
while i < row do 
  while j < col do 
  if input[i][j] == "X"
    if i == 0 || input[i-1][j] == "O" && j = 0 || input[i][j-1] == "O"
      counter += 1
    end 
  end 
  j += 1
 end
i += 1
end
  p counter
end 

matrix(
[
  ["X", "X", "O", "X"],
  ["X", "X", "O", "X"],
  ["O", "O", "X", "O"],
  ["O", "O", "X", "O"],
  ["O", "O", "O", "X"],
  ["O", "O", "O", "X"]
]
)
# expected output is 4

这不是家庭作业。我正在练习数据结构和算法。可以找到原始问题here

1 个答案:

答案 0 :(得分:2)

<强>算法

我们给出了一个数组input的等大小数组,称为&#34; rows&#34;。问题中给出的例子如下。

input = [
  ["X", "X", "O", "X"],
  ["X", "X", "O", "X"],
  ["O", "O", "X", "O"],
  ["O", "O", "X", "O"],
  ["O", "O", "O", "X"],
  ["O", "O", "O", "X"]
]

为方便起见,我将元素input[3][2] #=> "X"称为 3 2中的元素(甚至虽然Ruby没有二维数组或具有行和列的数组的概念。)

第一步是构造一个数组groups,每个元素(一个&#34; group&#34;)由一个元素组成,&#34; X&#34;的索引。在其中一行中:

groups
  #=> [[[0, 0]], [[0, 1]], [[0, 3]], [[1, 0]], [[1, 1]], [[1, 3]],
  #    [[2, 2]], [[3, 2]], [[4, 3]], [[5, 3]]] 

我们现在考虑groups[[5, 3]])的最后一个元素,并询问它的任何元素(只有一个,[5, 3])是否与其中的元素位于同一个岛中任何其他团体。我们发现它与组[4, 3]中的[[4, 3]]位于同一个岛上(因为元素位于同一列中,相隔一行)。因此,我们删除最后一个组并将其所有元素(此处只是一个)添加到组[[4, 3]]。我们现在有:

groups
  #=> [[[0, 0]], [[0, 1]], [[0, 3]], [[1, 0]], [[1, 1]], [[1, 3]],
  #    [[2, 2]], [[3, 2]], [[4, 3], [5, 3]]] 

我们现在使用现在的最后一个组[[4, 3], [5, 3]]重复该过程。我们必须确定该组中的任何一个元素是否与每个其他组中的任何元素位于同一个岛上。它们不是 1 。因此,我们确定了第一个岛屿,包括地点[4, 3][5, 3]

初始化islands = []后,我们执行以下操作:

islands << groups.pop

所以现在

groups
  #=>#=> [[[0, 0]], [[0, 1]], [[0, 3]], [[1, 0]], [[1, 1]], [[1, 3]],
  #       [[2, 2]], [[3, 2]]] 
islands
  #=> [[[4, 3], [5, 3]]]

我们继续这种方式,直到groups为空,此时islands的每个元素都是一个孤岛。

<强>代码

def find_islands(input)
  groups = input.each_with_index.with_object([]) { |(row,i),groups|
    row.each_with_index { |c,j| groups << [[i,j]] if c == 'X' } }
  islands = []
  while groups.any?
    last_group = groups.pop
    idx = groups.each_index.find { |idx| same_island?(groups[idx], last_group) }
    if idx.nil?
      islands << last_group
    else
      groups[idx] += last_group
    end
  end
  islands.map(&:sort)
end

def same_island?(group1, group2)
  group1.product(group2).any? { |(i1,j1),(i2,j2)|
  ((i1==i2) && (j1-j2).abs == 1) || ((j1==j2) && (i1-i2).abs == 1) }
end

示例

对于上面给出的数组input,我们获得了以下岛屿数组。

find_islands(input)
  #=> [[[4, 3], [5, 3]],
  #    [[2, 2], [3, 2]],
  #    [[0, 3], [1, 3]],
  #    [[0, 0], [0, 1], [1, 0], [1, 1]]]

<强>解释

对于没有经验的Rubiest来说,要学会理解我所提出的两种方法的运作方式,这是非常有用的。使用以下方法需要熟悉:

    模块Enumerable中的
  • each_with_indexany?findmapproduct
  • 课程Enumerator中的
  • each_indexwith_objectnext
  • 课程Array中的
  • popsort
  • 课程Integer中的
  • abs
  • 模块Kernel中的
  • nil?(记录在Object 2

groups

的初步计算

如果这是一个单独的方法(不是一个坏主意),我们会写下以下内容。

def construct_initial_groups(input)
  input.each_with_index.with_object([]) { |(row,i),groups|
    row.each_with_index { |c,`j| groups << [[i,j]] if c == 'X' } }
end

Ruby的新手可能会发现这有点压倒性。实际上,只是采用Ruby方式来收紧以下方法。

def construct_initial_groups(input)
  groups = []
  i = 0
  input.each do |row|
    j = 0
    row.each do |c|
      groups << [[i,j]] if c == 'X'
      j += 1
    end
    i += 1
  end
  groups
end

从这里到达那里的第一步是使用方法Enumerable#each_with_index

def construct_initial_groups(input)
  groups = []
  input.each_with_index do |row,i|
    row.each_with_index do |c,j|
      groups << [[i,j]] if c == 'X'
    end
  end
  groups
end

接下来,我们使用Enumerator#with_object方法获取construct_initial_groups上方的第一个表单。

编写块变量(|(row,i),groups|)可能仍然令人困惑。你会了解到

enum = input.each_with_index.with_object([])
  #=> #<Enumerator: #<Enumerator: [["X", "X", "O", "X"], ["X", "X", "O", "X"],
  #     ["O", "O", "X", "O"], ["O", "O", "X", "O"], ["O", "O", "O", "X"],
  #     ["O", "O", "O", "X"]]:each_with_index>:with_object([])>

枚举器,其元素是通过应用方法Enumerator#next生成的。

(row,i), groups = enum.next
  #=> [[["X", "X", "O", "X"], 0], []]

Ruby使用消歧分解为三个块变量中的每一个赋值:

row
  #=> ["X", "X", "O", "X"]
i #=> 0
groups
  #=> []

然后我们执行块计算。

row.each_with_index { |c,j| groups << [[i,j]] if c == 'X' }
  #=> ["X", "X", "O", "X"].each_with_index { |c,j| groups << [[i,j]] if c == 'X' }
  #=>  ["X", "X", "O", "X"]

所以现在

groups
  #=> [[[1, 0]], [[1, 1]], [[1, 3]]]

现在生成enum的第二个元素并将其传递给块并执行块计算。

(row,i), groups = enum.next
  #=> [[["O", "O", "X", "O"], 2], [[[1, 0]], [[1, 1]], [[1, 3]]]]
row
  #=> ["O", "O", "X", "O"]
i #=> 2
groups
  #=> [[[1, 0]], [[1, 1]], [[1, 3]]]
row.each_with_index { |c,j| groups << [[i,j]] if c == 'X' }
  #=> ["O", "O", "X", "O"]

现在groups有两个元素。

groups
  #=> [[[1, 0]], [[1, 1]],
  #    [[1, 3]], [[2, 2]]]

其余的计算方法类似。

same_island? method

假设

group1 #=> [[0, 0], [1, 0]]
group2 #=> [[0, 1], [1, 1]]

这告诉我们[0, 0][1, 0]位于同一个岛上,[0, 1][1, 1]位于同一个岛上,但我们还不知道是否所有岛屿四个坐标在同一个岛上。为了确定是否是这种情况,我们将查看所有坐标对,其中一个来自group1,另一个来自group2。如果我们比较[0, 0][1, 1],我们无法断定它们位于同一个岛屿(或不同的岛屿)上。但是,当我们比较[0, 0][0, 1]时,我们看到它们位于同一个岛上(因为它们位于相邻列中的同一行),因此我们推断两个组中的所有元素都在同一个岛屿。例如,我们可以将坐标从group2移动到group1,并从进一步的考虑中消除group2

现在考虑方法same_island?为这两个群体执行的步骤。

group1 = [[0, 0], [1, 0]]
group2 = [[0, 1], [1, 1]]

a = group1.product(group2)
  #=> [[[0, 0], [0, 1]], [[0, 0], [1, 1]], [[1, 0], [0, 1]],
  #    [[1, 0], [1, 1]]]

(i1,j1),(i2,j2) = a.first
   #=> [[0, 0], [0, 1]]
i1 #=> 0
j1 #=> 0
i2 #=> 0
j2 #=> 1
b = i1==i2 && (j1-j2).abs == 1
   #=> true
c = j1==j2 && (i1-i2).abs == 1
   #=> false
b || c
   #=> true

已经发现第一对坐标位于同一个岛上。 (事实上​​,由于发现cb,因此无法执行true的计算。)

find_islands发表了评论

为了展示所有内容如何组合在一起,我将为方法find_islands添加注释。

def find_islands(input)
  groups = input.each_with_index.with_object([]) { |(row,i),groups|
    row.each_with_index { |c,j| groups << [[i,j]] if c == 'X' } }
  islands = []
  # the following is the same as: until groups.empty?
  while groups.any?
    # pop removes and returns last element of `groups`. `groups` is modified    
    last_group = groups.pop
    # iterate over indices of `groups` looking for one whose members
    # are on the same island as last_group 
    idx = groups.each_index.find { |idx| same_island?(groups[idx], last_group) }
    if idx.nil?
      # last_group is an island, so append it to islands
      islands << last_group
    else
      # groups[idx] members are found to be on the same island as last_group,
      # so add last_group to group groups[idx]
      groups[idx] += last_group
    end
  end
  # sort coordinates in each island, to improve presentation 
  islands.map(&:sort)
end

1也就是说,没有元素groups[i,j][4, 3][5, 3]位于同一列中且一行相隔一行或位于同一行中一列开

2模块Kernel中的实例方法记录在类Object中。请参阅Kernel的前两段和Object的第二段。