Ruby将范围转换为数组的方法有多快?

时间:2015-07-15 18:43:50

标签: ruby

(1..999).to_a

这种方法是O(n)吗?我想知道转换是否涉及隐式迭代,因此Ruby可以将值逐个写入连续的内存地址。

2 个答案:

答案 0 :(得分:3)

The method实际上比O(n)略差。它不仅进行了天真的迭代,而且还没有提前检查大小是多少,因此它必须在迭代时重复分配更多内存。 I've opened an issue for that aspect已经在邮件列表上讨论过几次(并简要添加到ruby-core)。问题是,像Ruby中的几乎任何东西一样,Range可以打开并搞乱,因此Ruby无法真正优化该方法。它甚至不能指望Range#size返回正确的结果。更糟糕的是,有些枚举甚至将size方法委托给to_a

通常,没有必要进行此转换,但如果您确实需要数组方法,则可以使用Array#fill代替,这样可以填充(可能预先分配的)数组使用从其指数中得出的值。

答案 1 :(得分:2)

Range.instance_methods(false).include? :to_a
# false

范围没有to_a,它从Enumerable混合继承它,所以它通过一次推送一个值来构建数组。这似乎是非常低效的,但我会让基准测试说明一切:

require 'benchmark'

size = 100_000_000

Benchmark.bmbm do |r|
  r.report("range") { (0...size).to_a }
  r.report("fill") { a = Array.new(size); a.fill { |i| i } }
  r.report("array") { Array.new(size) { |i| i } }
end

# Rehearsal -----------------------------------------
# range   4.530000   0.180000   4.710000 (  4.716628)
# fill    5.810000   0.150000   5.960000 (  5.966710)
# array   7.630000   0.250000   7.880000 (  7.879940)
# ------------------------------- total: 18.550000sec
#
#             user     system      total        real
# range   4.540000   0.120000   4.660000 (  4.660249)
# fill    5.980000   0.110000   6.090000 (  6.089962)
# array   7.880000   0.110000   7.990000 (  7.985818)

这不奇怪吗?它实际上是最快的。只需使用构造函数,手动填充数组就会更快。

但正如Ruby的情况一样,不要过于担心。对于合理大小的范围,性能差异可以忽略不计。