使用 `inject`、`unless` 和 `next` 来确定最小值
Posted
技术标签:
【中文标题】使用 `inject`、`unless` 和 `next` 来确定最小值【英文标题】:Using `inject`, `unless`, and `next` to determine the minimum value 【发布时间】:2016-03-22 14:31:22 【问题描述】:我有这个代码:
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"
这里为什么不继续迭代@987654326@?
"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
之间的交互。没有比较运算符时,距离是如何比较的?
【问题讨论】:
我相信您将块中的累加器和元素混合到inject
上。根据您的 cmets,您希望它是 |element, accum|
,而恰恰相反:|accum, element|
。尝试将|a, b|
交换为|b, a|
。
“最接近”什么?
感谢铁皮人编辑我的原帖。这是我在 *** 上发布的第一个问题,原始格式不太理想。 Nearest 只是将“a”与所有其他“b”进行比较并选择最小的数字。这是从 Ruby 中 Dijkstra 算法的实现中脱离上下文的一小段代码。
【参考方案1】:
让我用纯 Ruby 解释Enumerable#inject
。请注意,以下代码不是 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
可以看到在循环中,前一次迭代的累加器和数组的当前元素被传递给了block的params,block的返回值被重新赋值给了累加器。
那么块中的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
当n
为2
时,Array#inject
被break
终止,并返回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
终止。
【讨论】:
感谢您抽出宝贵时间帮助我更好地了解注入方法。将它与 unless 结合使用确实令人困惑,但我想我现在对它的理解要好得多。【参考方案2】:
inject
的工作原理
传递给inject
的块在每次迭代中接收两个参数。第一个参数(此处为prev_nearest_key
)是一个“累加器”,其值是前一次迭代返回的任何值。 (对于第一次迭代,它将是提供给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?
第一行基本上是说,“如果前一个最近顶点的距离是nil
,那么它不是最近的,所以在下一次迭代中将prev_nearest_vertex
设置为current_vertex
。”第二行说“如果当前顶点的距离是nil
,那么它不是最近的,所以在下一次迭代中保持prev_nearest_vertex
不变。
这里是第三和第四行:
next prev_nearest_vertex if distances[prev_nearest_vertex] < distances[current_vertex]
current_vertex
这些可以改写成这样:
if distances[prev_nearest_vertex] < distances[current_vertex]
prev_nearest_vertex
else
current_vertex
end
它只是说,“如果距离较小,则在下一次迭代中将prev_nearest_vertex
设置为prev_nearest_vertex
;否则将其设置为current_vertex
。
这是相当不错的代码,但您可能应该...
改为这样做
Ruby 的 Enumerable 模块有很多非常有用的方法,包括一个叫做min_by
的方法。它为 Enumerable 中的每个元素评估给定块,并返回为其返回最小值的元素。为了演示,考虑这个map
:
words = ["lorem", "ipsum", "dolor", "sit", "amet"]
words.map |word| word.size
# => [5, 5, 5, 3, 4]
这只是将单词数组转换为它们大小的数组。现在假设我们想要得到最短的单词。使用min_by
很容易:
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
,得到最小距离对应的顶点:
def test(vertices, distances)
vertices.min_by |vertex| distances[vertex] || Float::INFINITY
end
test(vertices, distances)
# => 1
【讨论】:
以上是关于使用 `inject`、`unless` 和 `next` 来确定最小值的主要内容,如果未能解决你的问题,请参考以下文章
unless|until|LABEL|{}|last|next|redo| || |//|i++|++i
理解saltstack 里cmd.run 配合onlyif和unless使用