使用`inject`,`除非`和`next`来确定最小值

时间:2016-03-22 14:31:22

标签: ruby next inject

我有这段代码:

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时,我不理解nextnearest_vertex之间的互动。没有比较运算符时距离如何比较?

2 个答案:

答案 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。它终止当前的块调用,并且如果未明确指定返回值,则使块返回xnil。)

顺便说一下,块中的break x终止了获取块的方法调用,并使方法返回x。例如:

[1, 2, 3, 4].inject do |acc, n|
   break n if n == 2
   acc + n
end
=> 2

Array#injectbreak时,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

并且没有打印句子,因为fooreturn终止。

答案 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,因为它是上一次迭代返回的值,bcurrent_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