以最快的方式从ruby中的独占范围获得最大值
好的,假设你在ruby上有一个很大的范围。我想找到一种方法来获得范围内的最大值 范围是独占的(用三个点定义),这意味着它的结果中不包括结束对象。它可以由整数、字符串、时间或响应以最快的方式从ruby中的独占范围获得最大值,ruby,optimization,performance,range,Ruby,Optimization,Performance,Range,好的,假设你在ruby上有一个很大的范围。我想找到一种方法来获得范围内的最大值 范围是独占的(用三个点定义),这意味着它的结果中不包括结束对象。它可以由整数、字符串、时间或响应和#succ的任何对象组成。(这是范围内开始/结束对象的唯一要求) 以下是一个独占范围的示例: past = Time.local(2010, 1, 1, 0, 0, 0) now = Time.now range = past...now range.include?(now) # =>
和#succ
的任何对象组成。(这是范围内开始/结束对象的唯一要求)
以下是一个独占范围的示例:
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秒。但是,对象可能不是时间,甚至可能不支持-
。我更愿意找到一个有效的通用解决方案,但我愿意将特例代码与通用解决方案的回退结合起来(稍后将详细介绍)
如上所述,使用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#max
所做的那样),从而节省一点时间
我考虑的另一种方法是为常见的ruby类型(如Integer、String、Time、Date、DateTime)提供特殊的案例代码,然后将上述代码用作备用代码。这可能有点难看,但在遇到这些对象类型时可能效率更高,因为我可以使用从Range#last
的减法来获得最大值,而无需任何迭代
有人能想出比这更有效/更快的方法吗?我不确定速度(初始测试似乎也不太快),但下面可能会满足您的需要:
past = Time.local(2010, 1, 1, 0, 0, 0)
now = Time.now
range = past...now
range.to_a[-1]
非常基本的测试(在我的头脑中计数)表明,这大约需要4秒,而您提供的方法大约需要5-6秒。希望这有帮助
编辑1:删除了第二个解决方案,因为它完全错误。我能想到的最简单的解决方案,适用于包含范围和独占范围:
range.max
其他一些可能的解决办法:
range.entries.last
range.entries[-1]
这些解决方案都是O(n),在大范围内会非常缓慢。原则上的问题是Ruby中的范围值是使用succ
方法从一开始就对所有值进行迭代枚举的。元素不必实现返回上一个值的方法(即pred
)
最快的方法是找到最后一项的前置项(O(1)解决方案):
这仅适用于具有实现pred
的元素的范围。Ruby的更高版本为整数实现pred
。如果该方法不存在,您必须自己添加它(本质上相当于您建议的特殊情况代码,但实现起来稍微简单)
一些快速基准测试表明,对于大范围(在本例中,range=1…1000000
),最后一种方法在许多数量级上是最快的,因为它是O(1):
建议在注释中使用
range.last-(range.exclude_end??1:0)
。它确实适用于没有其他方法的日期,但永远不会适用于非数字范围<代码>字符串#-不存在,对于整数参数没有意义<但是,代码>字符串#pred。我认为没有任何不涉及枚举范围的方法可以实现这一点,至少如前所述,除非您有关于如何构造范围的其他信息,因此可以在不枚举的情况下推断所需的值。在所有的建议中,我选择了#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)
我注意到第三和第四个选项显著增加了系统时间。我认为这与显式创建数组有关,这似乎是避免使用它们的一个很好的理由,即使它们在运行时间上并不明显更昂贵。在最后一行中,您有
range.last.pred
。这对我来说不合适。也不包括range.last.prev
。请您解释一下这一部分好吗?所以请尝试使用pred
,如果失败,请尝试range。last-1
否则返回到另一个解决方案pred
是在Ruby的更高版本中为整数实现的(它在我的1.8.7-174中,应该在1.9+中)。它不适用于实现succ
的所有类,因此可能需要自己定义它。更紧凑、更具日期意识的实现如下:range.last-(range.exclude\u end±1:0)
但它仍然不那么可读,我必须说。@kirushik:range.last-1
与range.last.pred
不同。范围元素可以是实现succ
的任何元素,它们不一定有-
方法,更不用说接受Fixnum
参数的方法(例如String#-
不存在)。问题是,pred
在大多数类上也不存在。对于某些类,实现实际上可以像self-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)
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)