好的,所以说你在红宝石中有一个非常大的范围。我想找到一种方法来获得范围中的最大值。
范围是独占的(用三个点定义)意味着它不包括结果中的结束对象。它可以由Integer,String,Time或任何响应#<=>
和#succ
的对象组成。 (这是Range中开始/结束对象的唯一要求)
以下是专属范围的示例:
past = Time.local(2010, 1, 1, 0, 0, 0)
now = Time.now
range = past...now
range.include?(now) # => false
现在我知道我可以做这样的事情来获得最大值:
range.max # => returns 1 second before "now" using Enumerable#max
但这需要花费很多时间来执行。我也知道我可以从最终对象中减去1秒。但是,该对象可能不是Time,它甚至可能不支持#-
。我更愿意找到一个有效的通用解决方案,但我愿意将特殊案例代码与一般解决方案的后备结合起来(稍后将详细介绍)。
如上所述,使用Range#last
也不会有效,因为它是一个独占范围,并且不包括结果中的最后一个值。
我能想到的最快的方法是:
max = nil
range.each { |value| max = value }
# max now contains nil if the range is empty, or the max value
这类似于Enumerable#max
所做的(Range继承),除了它利用了每个值将大于前一个的事实,因此我们可以跳过使用#<=>
进行比较每个值与前一个(Range#max
的方式)节省了一点点时间。
我想到的另一种方法是为常见的ruby类型(如Integer,String,Time,Date,DateTime)提供特殊的案例代码,然后使用上面的代码作为后备。它有点难看,但遇到这些对象类型时效率可能更高,因为我可以使用Range#last
的减法来获得最大值而无需任何迭代。
有人能想到比这更有效/更快的方法吗?
答案 0 :(得分:8)
我能想到的最简单的解决方案,适用于包容性和独家范围:
range.max
其他一些可能的解决方案:
range.entries.last
range.entries[-1]
这些解决方案都是O(n),对于大范围来说非常慢。问题原则上是Ruby中的范围值是从所有值迭代地使用succ
方法枚举的,从头开始。元素不必须实现返回先前值的方法(即pred
)。
最快的方法是找到最后一项的前身(O(1)解决方案):
range.exclude_end? ? range.last.pred : range.last
对于包含实现pred
的元素的范围,这仅适用于 。更高版本的Ruby为整数实现pred
。如果它不存在,你必须自己添加方法(基本上等同于你建议的特殊情况代码,但实现起来稍微简单)。
一些快速基准测试表明,对于大范围(在这种情况下为range = 1...1000000
),最后一种方法是最快的数量级,因为它是O(1):
user system total real
r.entries.last 11.760000 0.880000 12.640000 ( 12.963178)
r.entries[-1] 11.650000 0.800000 12.450000 ( 12.627440)
last = nil; r.each { |v| last = v } 20.750000 0.020000 20.770000 ( 20.910416)
r.max 17.590000 0.010000 17.600000 ( 17.633006)
r.exclude_end? ? r.last.pred : r.last 0.000000 0.000000 0.000000 ( 0.000062)
在评论中建议使用range.last - (range.exclude_end? ? 1 : 0)
。它适用于没有其他方法的日期,但永远不会用于非数字范围。 String#-
不存在,并且对整数参数没有意义。但是,String#pred
can be implented。
答案 1 :(得分:1)
我不确定速度(并且初始测试看起来不会非常快),但以下可能会满足您的需求:
past = Time.local(2010, 1, 1, 0, 0, 0)
now = Time.now
range = past...now
range.to_a[-1]
非常基本的测试(在我脑海中计算)表明,你提供的方法需要大约4秒,大约需要5秒。希望这会有所帮助。
编辑1:删除了第二个解决方案,因为它完全错误。
答案 2 :(得分:1)
我不认为有任何方法可以实现这个不涉及枚举范围的方法,至少除非已经提到过,否则你有关于如何构造范围的其他信息,因此可以在没有枚举的情况下推断出所需的值。在所有建议中,我会选择#max
,因为它似乎最具表现力。
require 'benchmark'
N = 20
Benchmark.bm(30) do |r|
past, now = Time.local(2010, 2, 1, 0, 0, 0), Time.now
@range = past...now
r.report("range.max") do
N.times { last_in_range = @range.max }
end
r.report("explicit enumeration") do
N.times { @range.each { |value| last_in_range = value } }
end
r.report("range.entries.last") do
N.times { last_in_range = @range.entries.last }
end
r.report("range.to_a[-1]") do
N.times { last_in_range = @range.to_a[-1] }
end
end
user system total real
range.max 49.406000 1.515000 50.921000 ( 50.985000)
explicit enumeration 52.250000 1.719000 53.969000 ( 54.156000)
range.entries.last 53.422000 4.844000 58.266000 ( 58.390000)
range.to_a[-1] 49.187000 5.234000 54.421000 ( 54.500000)
我注意到第3和第4选项显着增加了系统时间。我希望这与显式创建一个数组有关,这似乎是避免它们的一个很好的理由,即使它们在经过的时间里显然不是那么昂贵。