我正在进行Ruby Koans的练习,我被以下Ruby的怪癖所震惊,我发现它真的无法解释:
array = [:peanut, :butter, :and, :jelly]
array[0] #=> :peanut #OK!
array[0,1] #=> [:peanut] #OK!
array[0,2] #=> [:peanut, :butter] #OK!
array[0,0] #=> [] #OK!
array[2] #=> :and #OK!
array[2,2] #=> [:and, :jelly] #OK!
array[2,20] #=> [:and, :jelly] #OK!
array[4] #=> nil #OK!
array[4,0] #=> [] #HUH?? Why's that?
array[4,100] #=> [] #Still HUH, but consistent with previous one
array[5] #=> nil #consistent with array[4] #=> nil
array[5,0] #=> nil #WOW. Now I don't understand anything anymore...
那么为什么数组[5,0]不等于数组[4,0]呢?当你从第(长度+1)个位置开始时,数组切片会表现得这么奇怪,有什么原因吗?
我同意这看起来像是奇怪的行为,但即使是array# slice的官方文档也在下面的“特殊情况”中演示了与你的例子相同的行为:
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[5, 1] #=> []
a[5..10] #=> []
不幸的是,即使他们对array# slice的描述似乎也没有提供任何关于它为什么以这种方式工作的见解:
元素引用—返回位于下标处的元素,或返回从start开始并继续为长度元素的子数组,或返回由range指定的子数组。负索引从数组的末尾开始计数(-1是最后一个元素)。如果索引(或起始索引)超出范围,则返回nil。
这与slice返回一个数组有关,相关的源文档来自array# slice:
* call-seq:
* array[index] -> obj or nil
* array[start, length] -> an_array or nil
* array[range] -> an_array or nil
* array.slice(index) -> obj or nil
* array.slice(start, length) -> an_array or nil
* array.slice(range) -> an_array or nil
这对我来说意味着,如果你给出的开始是越界的,它将返回nil,因此在你的例子数组[4,0]请求存在的第4个元素,但要求返回一个零元素的数组。当数组[5,0]请求一个越界的索引时,它返回nil。如果您还记得slice方法是返回一个新的数组,而不是改变原始的数据结构,那么这可能更有意义。
编辑:
在看过评论后,我决定编辑这个答案。当arg值为2时,Slice调用以下代码片段:
if (argc == 2) {
if (SYMBOL_P(argv[0])) {
rb_raise(rb_eTypeError, "Symbol as array index");
}
beg = NUM2LONG(argv[0]);
len = NUM2LONG(argv[1]);
if (beg < 0) {
beg += RARRAY(ary)->len;
}
return rb_ary_subseq(ary, beg, len);
}
如果你查看定义了rb_ary_subseq方法的array.c类,你会看到如果长度超出边界,它会返回nil,而不是索引:
if (beg > RARRAY_LEN(ary)) return Qnil;
在这个例子中,这就是传入4时发生的情况,它检查是否有4个元素,因此不会触发nil返回。然后,如果第二个参数被设置为0,它将返回一个空数组。而如果传入5,则数组中没有5个元素,因此在zero参数被求值之前返回nil。代码在第944行。
我认为这是一个错误,或者至少是不可预测的,而不是“最小意外原则”。当我有几分钟的时间,我至少会提交一个失败的测试补丁给ruby core。
我发现Gary Wright的解释也很有帮助。
http://www.ruby-forum.com/topic/1393096#990065
Gary Wright给出的答案是-
http://www.ruby-doc.org/core/classes/Array.html
文档当然可以更清楚,但实际的行为是
自我一致和有用。
注意:我假设1.9。X版本的字符串。
按照以下方式考虑编号会有所帮助:
-4 -3 -2 -1 <-- numbering for single argument indexing
0 1 2 3
+---+---+---+---+
| a | b | c | d |
+---+---+---+---+
0 1 2 3 4 <-- numbering for two argument indexing or start of range
-4 -3 -2 -1
常见的(也是可以理解的)错误是过于假定语义
的单参数索引的语义是相同的
两个参数场景中的第一个参数(或范围)。他们不是
在实践中也是一样的,但是文档并没有反映这一点。
错误肯定是在文档中,而不是在
实现:
单参数:索引表示单个字符位置
在字符串中。结果是单个字符串
在索引处或nil处找到,因为在给定位置没有字符
索引。
s = ""
s[0] # nil because no character at that position
s = "abcd"
s[0] # "a"
s[-4] # "a"
s[-5] # nil, no characters before the first one
两个整数参数:参数标识字符串的一部分
提取或替换。特别是字符串的零宽度部分
还可以进行标识,以便文本可以在之前或之后插入
现有字符,包括字符串的前端或末尾。在这个
在情况下,第一个参数没有标识字符位置,但是
而是标识字符之间的空格,如图所示
以上。第二个参数是长度,可以是0。
s = "abcd" # each example below assumes s is reset to "abcd"
To insert text before 'a': s[0,0] = "X" # "Xabcd"
To insert text after 'd': s[4,0] = "Z" # "abcdZ"
To replace first two characters: s[0,2] = "AB" # "ABcd"
To replace last two characters: s[-2,2] = "CD" # "abCD"
To replace middle two characters: s[1..3] = "XX" # "aXXd"
值域的行为非常有趣。起点是
当提供两个参数时,与第一个参数相同(如所述)
但范围的终点可以是'字符位置'为
与单索引或“边缘位置”作为两个整数
参数。区别是由是否双点范围决定的
或者使用三重点范围:
s = "abcd"
s[1..1] # "b"
s[1..1] = "X" # "aXcd"
s[1...1] # ""
s[1...1] = "X" # "aXbcd", the range specifies a zero-width portion of
the string
s[1..3] # "bcd"
s[1..3] = "X" # "aX", positions 1, 2, and 3 are replaced.
s[1...3] # "bc"
s[1...3] = "X" # "aXd", positions 1, 2, but not quite 3 are replaced.
如果你回顾这些例子并坚持使用单音
双索引或范围索引示例的索引语义
感到困惑。你必须使用我在
ASCII图来模拟实际行为。
考虑以下数组:
>> array=["a","b","c"]
=> ["a", "b", "c"]
您可以通过将一个项分配给a[0,0]来将其插入到数组的开始(头部)。要将元素放在“a”和“b”之间,使用a[1,0]。基本上,在表示法a[i,n]中,i表示一个索引,n表示一些元素。当n=0时,它定义了数组元素之间的位置。
现在,如果您考虑数组的末尾,您如何使用上面描述的符号将一个项附加到它的末尾?很简单,将值赋给a[3,0]。这是数组的尾部。
因此,如果您尝试访问a[3,0]处的元素,则会得到[]。在这种情况下,您仍然在数组的范围内。但是如果你试图访问a[4,0],你会得到nil作为返回值,因为你不再在数组的范围内了。
更多信息请访问http://mybrainstormings.wordpress.com/2012/09/10/arrays-in-ruby/。