我有这个代码:
def test(vertices, distances)
until vertices.empty?
nearest_vertex = vertices.inject do |a, b|
p "a = #{a}: b = #{b}"
p "distances[a] = #{distances[a]}, distances[b] = #{distances[b]}"
next b unless distances[a] #next b if distances[a] == true
next a unless distances[b] #next a if distances[b] == true
next a if distances[a] < distances[b]
p "b = #{b}"
b
end
p "nearest_vertex = #{nearest_vertex}"
vertices.delete nearest_vertex
end
end
vertices = [1, 2, 3, 4, 5, 6]
distances = {1 => 0, 2 => 3, 3 => 2, 4 => 18, 5 => nil, 6 => 7}
test(vertices, distances)
其输出:
"a = 1: b = 2"
"distances[a] = 0, distances[b] = 3"
"a = 1: b = 3"
"distances[a] = 0, distances[b] = 2"
...
"a = 1: b = 6"
"distances[a] = 0, distances[b] = 7"
"nearest_vertex = 1"
这里,b = 6
没有打印。这是因为next
发出了停止迭代命令吗?
"a = 2: b = 3"
"distances[a] = 3, distances[b] = 2"
"b = 3"
为什么迭代在这里不继续到a=2: b=4
?
"a = 3: b = 4"
"distances[a] = 2, distances[b] = 18"
"a = 3: b = 5"
"distances[a] = 2, distances[b] = "
"a = 3: b = 6"
"distances[a] = 2, distances[b] = 7"
"nearest_vertex = 3"
...
一旦a
被设置为3
,一切都像我认为的那样工作。程序怎么知道nearest_vertex
是3?
我不理解inject
和next
在确定如何以及何时将顶点声明为nearest_vertex
时的相互作用。当没有比较运算符时,如何比较距离?
2条答案
按热度按时间jk9hmnmh1#
让我来解释一下纯Ruby中的
Enumerable#inject
。请注意,以下代码不是Enumerable#inject
的原始实现。为了清楚起见,我将在类Array
中解释它,并重点介绍最基本的用法ary.inject(&block)
:可以看到,在循环中,前一次迭代的累加器和数组的当前元素被传递给块的参数,块的返回值被重新分配给累加器。
那么块中的
next x
是什么?你可以把一个块看作一个匿名函数,关键字
next
就是它的return
。它终止当前块调用,并使块返回x
(如果未显式指定返回值,则返回nil
)。顺便说一下,块中的
break x
终止了接受该块的方法调用,并使该方法返回x
。例如:当
n
是2
时,Array#inject
被break
终止,并返回n
。块中的
return
终止方法调用,该方法调用接受该块的方法。例如:而且没有打印句子,因为
foo
以return
结尾。xqnpmsa82#
inject
工作原理传递给
inject
的块在每次迭代中接收两个参数。第一个参数(这里是prev_nearest_key
)是一个“累加器”,其值是上一次迭代返回的任何值。(对于第一次迭代,它将是给定给inject
的参数,或者,这里没有它,集合的第一个元素vertices[0]
。)第二个参数(key
)是集合的当前元素。当迭代完成时,返回累加器的最终值。当您在传递给迭代器的块中调用
next val
时,val
立即从块中返回,并开始下一次迭代。为了演示,下面是map
的外观:在上面,当
letter
是元音时,letter.upcase
从块返回,并且下一行永远不会被计算。当letter
不是元音时,它将从块中返回,不作任何更改。下面是一个类似的inject示例:
这里有什么不同吗当
letter
是元音时,accum + letter.upcase
从块中返回(实际上是将letter.upcase
附加到accum
),并且下一行永远不会被计算。当letter
不是元音时,accum + letter
将从块中返回。在这两种情况下,从块返回的值在下一次迭代中都变成了accum
。代码是如何工作的
这是您的代码,但使用了更易于理解的变量名。
我将累加器
a
重命名为prev_nearest_vertex
,因为它是上一次迭代返回的值,并将b
重命名为current_vertex
。块内的前两行只是检查
distances[prev_nearest_vertex]
和distances[current_vertex]
是否是nil
。它们可以(也许应该)这样写:第一行基本上是说,“如果前一个最近的顶点的距离是
nil
,那么它不是最近的,所以在下一次迭代中将prev_nearest_vertex
设置为current_vertex
。”第二行说“如果当前顶点的距离是nil
,那么它不是最近的,所以在下一次迭代中保持prev_nearest_vertex
不变。下面是第三行和第四行:
可以这样重写:
它只是说,“在下一次迭代中将
prev_nearest_vertex
设置为prev_nearest_vertex
,如果它的距离更小;否则将其设置为current_vertex
。这是一个很好的代码,但你可能应该...
改为
Ruby的Enumerable模块有很多非常有用的方法,包括一个叫做
min_by
的方法。它为Enumerable中的每个元素计算给定块,并返回返回最低值的元素。为了演示,考虑map
:这只是将一个单词数组转换为它们大小的数组。现在假设我们想得到最短的单词。使用
min_by
很简单:它不返回单词的大小,而是计算它们的大小,然后返回大小最小的单词。
这直接适用于您的代码。再次考虑
map
操作:这会产生一个对应于
vertices
中元素的距离数组,但nil
距离被Float::INFINITY
替换。这很有用,因为n < Float::INFINITY
对每个数字n
都为真。和前面一样,我们现在可以用min_by
替换map
,以获得对应于最小距离的顶点: