问题 用于访问数组中第一个/最后一个元素的Ruby约定[关闭]


这是一个关于惯例的问题。以下两组命令返回相同的结果。

a = [1, 2, 3]

a.first  # => 1
a[0]     # => 1

a.last   # => 3
a[-1]    # => 3

在Ruby,显式索引或函数中哪些是首选的?当然,假设这是在代码中 总是 访问第一个或最后一个元素。

注意:我一直在考虑每个周期都会采取的周期。因为 first 和 last 接受参数,他们会有更多的开销,但我不知道这是否会影响社区的偏好。

谢谢!


编辑

如果你看过这篇文章的评论,我的最后一段就有一个很大的争论。虽然我没记住这一点 [x] 相当于 .[](x)我的结论是正确的,第一个和最后一个有更多的开销。考虑到两者的性质,我认为这是由于参数检查 first/last。这些需要检查是否存在参数 [] 可以假设它们存在。

require 'benchmark'

a = [1..1000]

MAX = 1000000

Benchmark.bm(15) do |b|
  b.report("small first") { MAX.times do; a.first; end }
  b.report("small [0]")   { MAX.times do; a[0];    end }
  b.report("small last")  { MAX.times do; a.last;  end }
  b.report("small [-1]")  { MAX.times do; a[-1];   end }
end

a = [1..100000000000]

Benchmark.bm(15) do |b|
  b.report("large first") { MAX.times do; a.first; end }
  b.report("large [0]")   { MAX.times do; a[0];    end }
  b.report("large last")  { MAX.times do; a.last;  end }
  b.report("large [-1]")  { MAX.times do; a[-1];   end }
end

结果

                      user     system      total        real
small first       0.350000   0.000000   0.350000 (  0.901497)
small [0]         0.330000   0.010000   0.340000 (  0.857786)
small last        0.370000   0.000000   0.370000 (  1.054216)
small [-1]        0.370000   0.000000   0.370000 (  1.137655)
                      user     system      total        real
large first       0.340000   0.010000   0.350000 (  0.897581)
large [0]         0.320000   0.010000   0.330000 (  0.889725)
large last        0.350000   0.000000   0.350000 (  1.071135)
large [-1]        0.380000   0.000000   0.380000 (  1.119587)

4912
2017-08-13 14:45


起源

我相信它是.first和.last因为它使它更容易准备,而ruby是一种意味着要阅读的语言。此外,它还使人们更容易浏览您的代码并知道您在做什么 - Tall Paul
这可能是一个意见问题,而不是“什么是最好的”,但我更喜欢 first 和 last 如果这正是我的意思。如果您真的关心实时性能,那么Ruby可能不是您编写脚本语言的最佳选择。 ;) - lurker
我们用 first 和 last 因为它们可用。它们是可用的,因为它们需要更频繁。 Rails中提供了数组位置的其他访问器,但它们实际上没那么有用或可读,尽管人们可以使用它们,但我们看到 [n] 更经常的表示法。阅读并不是那么难 [n],它更加实用,因为它们易于在循环中使用。 - the Tin Man
从 Ruby风格指南: 从数组访问第一个或最后一个元素时,请选择 first 要么 last 过度 [0] 要么 [-1]。 - Stefan
我不明白你的最后一段。 [] 也接受参数。 - sawa


答案:


代码读取的次数超过了写入次数,和 first 和 last 花费较少的精力来理解,特别是对于经验不足的Ruby程序员或来自具有不同索引语义的语言的人。

虽然大多数程序员会立即知道这些是相同的:

a.first
a[0]

第一个仍然更容易阅读。阅读的难度并没有显着差异,但它就在那里。

last 是另一个问题。访问索引0将以几乎任何语言为您提供数组的第一个元素。但负面索引仅适用于某些语言。如果一个具有最小Ruby经验的C程序员试图读取我的代码,那么他们会更快地理解吗?:

a.last
a[-1]

负面指数可能会迫使他们进行谷歌搜索。


10
2017-08-13 15:01



当我用这种风格开始Ruby时,我遇到了同样的问题 a[-1]...... :) :) - Arup Rakshit
@Babai:我也是。这就是为什么它对它更强大的用途有好处(例如。 a[0..-1])但不是为了获得最后一个元素。 - Linuxios
一个C程序员也会做鬼脸并想一想“哦,天哪,他们正在阅读随机数据!” (地址为 - sizeof(a)的数据)。 - James R.
Downvoter?交代? - Linuxios
@JamesR。是的。这不是直观的表示法。 - Linuxios


由于Matz在其他几种语言之后设计了Ruby,我认为这些约定来自其他语言。

在Lisp,一个Ruby的鼓舞人心的父母,你会使用接近的东西 last 和 first 方法所以我会说 last 和 first 是惯例。

我才真正使用 first 和 last。我看到许多程序使用这些方法,但最终它是你的选择。这就是Ruby的美丽;)


2
2017-08-13 14:55





从速度的角度来看,对于更大的阵列, first 和 last 比...更快 []。对于较小的阵列,它是另一种方式。

大阵列:

array = (0..100000000).to_a

t = Time.now
10.times{array[0]}
puts Time.now - t
# => 0.000225356

t = Time.now
10.times{array.first}
puts Time.now - t
# => 2.9736e-05

t = Time.now
10.times{array[-1]}
puts Time.now - t
# => 7.847e-06

t = Time.now
10.times{array.last}
puts Time.now - t
# => 6.174e-06

小阵列:

array = (0..100).to_a

t = Time.now
10.times{array[0]}
puts Time.now - t
# => 4.403e-06

t = Time.now
10.times{array.first}
puts Time.now - t
# => 5.933e-06

t = Time.now
10.times{array[-1]}
puts Time.now - t
# => 4.982e-06

t = Time.now
10.times{array.last}
puts Time.now - t
# => 5.411e-06

为了便于书写/阅读, first 和 last 可以不带参数使用,不像 [],所以它们更简单。

有时,使用 first 和 last 让事情变得更容易,但很难 []:例如, array.each_slice(3).map(&:first)


2
2017-08-13 15:16



请提供测试以验证 first 和 last 更快。直观地说,我认为它们会变慢。 - screenmutt
我们能看到基准吗?我很好奇 ;) - Linuxios
我刚刚在我的问题中添加了基准测试。 - screenmutt
我赞成你的答案,但是@Linuxios有一个更好的答案,因为它包含了更多关注和解释为什么约定更容易。用于测试的道具。 BTW benchmark 是红宝石最好的包! - screenmutt
是的,我认为这是标准,但它的语法对我来说有点复杂。在我想要的时候,我不会立即想到如何使用它。 - sawa