【问题标题】:Why [20, ..., 13, 14].min(2) => [13, 20]?为什么 [20, ..., 13, 14].min(2) => [13, 20]?
【发布时间】:2015-11-14 07:15:28
【问题描述】:
[20, 32, 32, 21, 30, 25, 29, 13, 14].min(2)
# => [13, 20]

为什么不是[13, 14]?以及如何我得到我想要的,两个最小的元素(线性时间)?

The doc 的句子 “如果给出 n 参数,则最少 n 个元素作为数组返回” 我不太清楚,但我认为它说 min(2) 应该给我最小的两个元素。我找不到太多关于它的信息,但 this thread,这可能是起源,似乎同意我的观点,并说它应该返回与 sort.first(n) 相同的返回值,但事实并非如此:

[20, 32, 32, 21, 30, 25, 29, 13, 14].sort.first(2)
# => [13, 14]

对不起,如果是愚蠢的问题,对“大”示例感到抱歉,但这已经减少了 - 再删除一个数字(13 或 14 除外)确实给了我[13, 14]

【问题讨论】:

  • 这是 2.2 中的新方法,我怀疑实现中存在需要修复的错误。事实上,您有问题的 Array 的许多变体确实得到了正确答案,包括只是交换 13 和 14 的顺序。
  • 这里是 min 的源代码,如果有人想调试它:github.com/ruby/ruby/blob/…
  • 我也有同样的问题。我认为你是对的,我发现min_by(2){|i| i} 也返回了错误的结果。
  • 问题跟踪器中的相同问题我无法找到任何关于它的信息bugs.ruby-lang.org/issues
  • 我刚刚报告了错误here

标签: ruby min


【解决方案1】:

我刚刚在Ruby Issue Tracking System 中发布了对该错误的解释:

我想我找到了问题所在。以第一个例子:

[20, 32, 32, 21, 30, 25, 29, 13, 14].min(2)

这将调用文件“enum.c”中的函数“nmin_run”,它 将“bufmax”设置为我们想要的最小值 (n) 数量的 4 倍(对于 例如,bufmax 为 8),然后在该行中 1327 会打电话 原始数组的每个元素的函数“nmin_i”。

在函数“nmin_i”中,当缓冲区满时(“data->curlen == data->bufmax"),调用函数“nmin_filter”。在示例中, 当 curlen 为 8 时会发生这种情况,因此缓冲区为 [20, 32, 32, 21, 30、25、29、13]。 “nmin_filter”将进行快速排序,直到 n 到目前为止最小的元素在缓冲区的最左边,并且 将丢弃其余的元素,剩下 [20, 13] 在缓冲区中。

现在开始问题。在“nmin_filter”的末尾限制 (显然是为了将最大的价值存储在 buffer) 设置为缓冲区中的最后一个值(在示例中为 13), 这不是真的。然后基于该值“nmin_i”将丢弃 其余所有大于该值的元素(在示例中,丢弃 14)。然后对缓冲区进行排序并返回:

[13, 20]

所以解决方案要么删除所有与限制相关的部分,要么采取 最后一个枢轴作为限制。

【讨论】:

  • 谢谢。我看了一下代码,但这对我来说太多了。我会相信你和赞成的:-)。但是我是否正确理解(对于n = 2)它通过具有长度.min(n) 的最坏情况/平均运行时复杂度吗?
  • 从技术上讲,它不会对缓冲区进行完整的快速排序,而只是快速选择。每次缓冲区(大小为4n)已满时都会执行一次快速选择,并将留下n 最小的元素。因此,每个3n 元素都会有一个快速选择(平均时间O(4n) 和最坏情况O(16n²))(缓冲区已经有来自上次快速选择的n 元素)。最后,min 对剩余的n 元素进行排序。因此,假设原始数组的长度为L,则平均值。时间将是O(4/3 L + n log n) 和最坏情况O(16/3 n L + n²)。希望我没有错过任何东西。 :)
  • 再次感谢,现在算法对我来说已经很清楚了,我同意你的运行时分析。最坏的情况比理想情况更糟糕,但对于我固定的 n=2 特殊情况,即使这并不重要。而且我用大的增加/减少/随机数组和中到大的 n 做了一些测试,实际上并不能让它像 nL 那样慢,所以我猜算法至少在这些情况下以某种方式避免了它(我没有看到随机化或单调性检查左右,但我还是没有看得太仔细)。
  • @StefanPochmann 如果您的情况可能是性能问题,请在另一个答案中查看我的更新。
【解决方案2】:

顺便回答一下你的问题……

以及如何我得到我想要的,两个最小的元素(线性时间)?

如果这个方法不存在或者同时它被破坏了,你可以使用Quickselect在线性时间内选择两个最小的元素,这基本上是Ruby在min的底层所做的。

这是我从维基百科的直接翻译:

class Array
  def mymin(n)
    return self.sort if self.size <= n

    a = self.dup
    left = 0
    right = a.size - 1
    loop do
      pivot_index = left + (right - left) / 2;
      pivot_value = a[pivot_index]
      a[pivot_index], a[right] = a[right], a[pivot_index]
      store_index = left
      left.upto(right - 1).each do |i|
         if a[i] < pivot_value
           a[store_index], a[i] = a[i], a[store_index]
           store_index += 1
         end
      end
      a[right], a[store_index] = a[store_index], a[right]
      if n - 1 == store_index
        break
      elsif n - 1 < store_index
        right = store_index - 1
      else
        left = store_index + 1
      end
    end
    a.take(n).sort
  end
end

然后我们试试你的例子:

[20, 32, 32, 21, 30, 25, 29, 13, 14].mymin(2)
# => [13, 14]

耶!我们刚刚修复了min。但请注意,此实现的空间复杂度与原始数组的大小成线性关系,而 Ruby 实现与值 n 成线性关系。此外,如果您的原始数组有太多重复,这将有一个糟糕的表现,你应该寻找
3路分区。


如果您只想要 n = 2 的 min 并且真的担心性能,则可以针对这种情况制作优化版本,保证 O(L) (假设 L 是数组的长度)。

class Array
  def min2
    m1 = nil
    m2 = nil
    self.each do |x|
      if m1.nil? || x < m1
        m2 = m1
        m1 = x
      elsif m2.nil? || x < m2
        m2 = x
      end
    end
    [m1, m2].compact
  end
end

并以类似的方式使用它:

[20, 32, 32, 21, 30, 25, 29, 13, 14].min2
# => [13, 14]

【讨论】:

  • 谢谢,虽然我现在真的只需要最小的两个,而不是最小的n,所以写我自己的快速选择(我已经完成了之前)是矫枉过正。特别是保证线性时间(而不是二次)的版本。我希望我只是做错了并且有正确的方法(希望一旦修复错误就会有)。现在也许我会使用[...].reduce([]) { |a, n| (a &lt;&lt; n).sort.first(2) }。抱歉,我应该明确表示我自己知道如何做。
  • @StefanPochmann 同意!对于小型n 来说,这是一个很好的解决方案,就像您的情况一样。对于一个大的n,这将变成几乎二次方的时间。为您的简单解决方案点赞。
猜你喜欢
  • 1970-01-01
  • 2022-01-13
  • 2022-01-07
  • 2015-04-07
  • 2012-05-07
  • 1970-01-01
  • 1970-01-01
  • 2017-10-07
  • 1970-01-01
相关资源
最近更新 更多