我正在进行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)个位置开始时,数组切片会表现得这么奇怪,有什么原因吗?
我发现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图来模拟实际行为。
当你考虑数组slice可以是一个有效的左值,而不仅仅是一个右值时,这是有意义的:
array = [:peanut, :butter, :and, :jelly]
# replace 0 elements starting at index 5 (insert at end or array):
array[4,0] = [:sandwich]
# replace 0 elements starting at index 0 (insert at head of array):
array[0,0] = [:make, :me, :a]
# array is [:make, :me, :a, :peanut, :butter, :and, :jelly, :sandwich]
# this is just like replacing existing elements:
array[3, 4] = [:grilled, :cheese]
# array is [:make, :me, :a, :grilled, :cheese, :sandwich]
如果数组[4,0]返回nil而不是[],这将是不可能的。然而,array[5,0]返回nil,因为它是越界的(插入4元素数组的第4个元素后是有意义的,但插入4元素数组的第5个元素后是没有意义的)。
将切片语法数组[x,y]解读为“从数组中的x个元素开始,选择最多y个元素”。只有当数组至少有x个元素时才有意义。
dr:在Array .c的源代码中,不同的函数会被调用,这取决于你是将1个还是2个参数传递给array# slice,从而导致意外的返回值。
(首先,我想指出的是,我不会用C编程,但多年来一直在使用Ruby。因此,如果您不熟悉C语言,但您花了几分钟时间来熟悉函数和变量的基础知识,那么遵循Ruby源代码实际上并不难,如下所示。这个答案是基于Ruby v2.3的,但与v1.9差不多。)
场景# 1
数组中。长度== 4;Array.slice (4) #=> nil
如果你查看array# slice (rb_ary_aref)的源代码,你会看到当只传入一个参数时(第1277-1289行),rb_ary_entry被调用,传入索引值(可以是正的,也可以是负的)。
然后Rb_ary_entry计算请求元素从数组开始的位置(换句话说,如果传入了一个负索引,它计算正的等效值),然后调用rb_ary_elt来获得请求的元素。
正如预期的那样,当数组len的长度小于或等于索引(这里称为offset)时,rb_ary_elt返回nil。
1189: if (offset < 0 || len <= offset) {
1190: return Qnil;
1191: }
场景# 2
array.length == 4; array.slice(4, 0) #=> []
然而,当传入2个参数(即起始索引beg和切片len的长度)时,将调用rb_ary_subseq。
在rb_ary_subseq中,如果起始索引beg大于数组长度alen,则返回nil:
1208: long alen = RARRAY_LEN(ary);
1209:
1210: if (beg > alen) return Qnil;
否则,计算结果切片len的长度,如果它被确定为零,则返回一个空数组:
1213: if (alen < len || alen < beg + len) {
1214: len = alen - beg;
1215: }
1216: klass = rb_obj_class(ary);
1217: if (len == 0) return ary_new(klass, 0);
因为4的起始下标不大于array。长度,返回一个空数组,而不是人们可能期望的nil值。
问题回答吗?
如果这里真正的问题不是“什么代码导致这种情况发生?”,而是“Matz为什么这样做?”,那么你只需要在下一次RubyConf上请他喝杯咖啡,然后问他。
我发现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图来模拟实际行为。