我有这段代码:
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
是三个?
在确定如何以及何时将顶点声明为inject
时,我不理解next
和nearest_vertex
之间的互动。没有比较运算符时距离如何比较?
答案 0 :(得分:4)
让我在纯Ruby中解释Enumerable#inject
。请注意,以下代码 NOT 是Enumerable#inject
的原始实现。为清楚起见,我将在课程Array
中对其进行解释,并专注于最基本的用法ary.inject(&block)
:
class Array
def inject(&block)
return nil if self.empty?
enumerator = self.each
accumulator = enumerator.next
loop do
begin
accumulator = block.call(accumulator, enumerator.next)
rescue StopIteration
break
end
end
accumulator
end
end
你可以看到,在循环中,前一次迭代的累加器和数组的当前元素被传递给块的参数,并且块的返回值被重新分配给累加器。 / p>
那么块中的next x
是什么?
您可以将块视为匿名函数,关键字next
是其return
。它终止当前的块调用,并且如果未明确指定返回值,则使块返回x
(nil
。)
顺便说一下,块中的break x
终止了获取块的方法调用,并使方法返回x
。例如:
[1, 2, 3, 4].inject do |acc, n|
break n if n == 2
acc + n
end
=> 2
当Array#inject
为break
时,n
终止2
,并返回n
。
return
终止方法调用,该方法调用调用获取块的方法。例如:
def foo
[1, 2, 3].inject do |acc, n|
return n
end
puts 'You will never see this this sentence.'
end
foo
=> 2
并且没有打印句子,因为foo
由return
终止。
答案 1 :(得分:3)
inject
如何运作传递给inject
的块在每次迭代中都会收到两个参数。第一个参数(此处prev_nearest_key
)是&#34;累加器&#34;其值是上一次迭代返回的任何值。 (对于第一次迭代,它将是inject
给出的参数,或者在缺席的情况下,集合的第一个元素 - vertices[0]
在这里。)第二个参数(key
)是当前的集合的元素。迭代完成后,将返回累加器的最终值。
当您在传递给迭代器的块中调用next val
时,会立即从块返回val
并开始下一次迭代。为了演示,请查看map
的外观:
["h", "e", "l", "l", "o"].map do |letter|
next letter.upcase if "aeoiu".include?(letter)
letter
end
# => ["h", "E", "l", "l", "O"]
在上面,当letter
是元音时,从块返回letter.upcase
并且永远不会评估下一行。当letter
不是元音时,它会从块中返回。
这是一个类似注入的例子:
["h", "e", "l", "l", "o"].inject do |accum, letter|
next accum + letter.upcase if "aeoiu".include?(letter)
accum + letter
end
# => "hEllO"
这里有什么不同?如果letter
是元音,则会从块中返回accum + letter.upcase
(有效地将letter.upcase
附加到accum
),并且永远不会评估下一行。如果letter
不是元音,则从块中返回accum + letter
。在这两种情况下,块中返回的值在下一次迭代中变为accum
。
这是您的代码,但具有更易理解的变量名称。
nearest_vertex = vertices.inject do |prev_nearest_vertex, current_vertex|
next current_vertex unless distances[prev_nearest_vertex]
next prev_nearest_vertex unless distances[current_vertex]
next prev_nearest_vertex if distances[prev_nearest_vertex] < distances[current_vertex]
current_vertex
end
我已将累加器a
重命名为prev_nearest_vertex
,因为它是上一次迭代返回的值,b
是current_vertex
}。
块内的前两行只是检查distances[prev_nearest_vertex]
和distances[current_vertex]
是nil
。他们可以(也许应该)这样写出来:
next current_vertex if distances[prev_nearest_vertex].nil?
next prev_nearest_vertex if distances[current_vertex].nil?
第一行基本上是,&#34;如果前一个最近顶点的距离是nil
,那么它不是最近的,所以将prev_nearest_vertex
设置为{{ 1}}在下一次迭代中。&#34;第二行说&#34;如果当前顶点的距离是current_vertex
,那么它不是最近的,所以在下一次迭代中保持nil
相同。
以下是第三和第四行:
prev_nearest_vertex
这些可以像这样重写:
next prev_nearest_vertex if distances[prev_nearest_vertex] < distances[current_vertex]
current_vertex
它只是说,&#34;如果距离较小,则在下一次迭代中将if distances[prev_nearest_vertex] < distances[current_vertex]
prev_nearest_vertex
else
current_vertex
end
设置为prev_nearest_vertex
;否则将其设置为prev_nearest_vertex
。
这是非常好的代码,但你应该......
Ruby的Enumerable模块有很多非常有用的方法,包括一个名为min_by
的方法。它为Enumerable中的每个元素计算给定块,并返回返回最低值的元素。要进行演示,请考虑此current_vertex
:
map
这只是将一个单词数组转换为它们大小的数组。现在假设我们想要得到最短的词。 words = ["lorem", "ipsum", "dolor", "sit", "amet"]
words.map {|word| word.size }
# => [5, 5, 5, 3, 4]
:
min_by
而不是返回单词&#39; size,这会计算它们的大小,然后返回大小最小的单词。
这直接适用于您的代码。再次考虑words = ["lorem", "ipsum", "dolor", "sit", "amet"]
words.min_by {|word| word.size }
# => "sit"
操作:
map
这将生成与vertices = [1, 2, 3, 4, 5, 6]
distances = { 1 => 0, 2 => 3, 3 => 2, 4 => 18, 5 => nil, 6 => 7 }
vertices.map do |vertex|
distances[vertex] || Float::INFINITY
end
# => [0, 3, 2, 18, Infinity, 7]
中的元素对应的距离数组,但vertices
距离将替换为nil
。这很有用,因为Float::INFINITY
对于每个数字n < Float::INFINITY
都是正确的。和以前一样,我们现在可以用n
替换map
来获取与最小距离对应的顶点:
min_by