Arrays Ruby中的数组切片:不合逻辑行为的解释(摘自Rubykoans.com)
我正在进行中的练习,我发现以下Ruby怪癖真的无法解释:Arrays Ruby中的数组切片:不合逻辑行为的解释(摘自Rubykoans.com),arrays,ruby,Arrays,Ruby,我正在进行中的练习,我发现以下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 = [: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)第1个位置开始时,是否有任何原因导致数组切片行为如此奇怪???切片和索引是两种不同的操作,从一种行为推断另一种行为是您的问题所在
slice中的第一个参数不是标识元素,而是标识元素之间的位置,定义跨度(而不是元素本身):
4仍然在数组中,只是勉强;如果请求0个元素,则得到数组的空端。但是没有索引5,所以不能从那里切片
当你做索引时(比如
array[4]
),你指向的是元素本身,所以索引只能从0到3。我同意这似乎是奇怪的行为,但在下面的“特殊情况”中,甚至表现出与你的示例相同的行为:
不幸的是,即使是他们对Array#slice
的描述,似乎也没有提供任何关于它为何以这种方式工作的见解:
Element Reference返回索引处的元素,或返回从开始到长度元素的子数组,或返回由range指定的子数组。负索引从数组末尾向后计数(-1是最后一个元素)。如果索引(或起始索引)超出范围,则返回nil
至少要注意,行为是一致的。从5岁起,一切都一样;这种奇怪只发生在
[4,N]
处
也许这个模式有帮助,或者也许我只是太累了,根本没有帮助
array[0,4] => [:peanut, :butter, :and, :jelly]
array[1,3] => [:butter, :and, :jelly]
array[2,2] => [:and, :jelly]
array[3,1] => [:jelly]
array[4,0] => []
在
[4,0]
处,我们看到了数组的末尾。事实上,如果最后一个返回nil
,我会觉得很奇怪,因为模式中的美是如此。由于这样的上下文,4
是第一个参数的可接受选项,因此可以返回空数组。但是,一旦我们达到5以上,该方法可能会立即退出,因为它完全超出了边界。这与slice返回数组这一事实有关,来自array#slice的相关源文档:
这对我来说意味着,如果你给出了一个超出范围的开始,它将返回nil,因此在你的例子中,array[4,0]
请求存在的第四个元素,但请求返回一个零元素的数组。而数组[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);
}
在本例中,这就是传入4时发生的情况,它检查是否有4个元素,因此不会触发nil返回。然后,如果第二个参数设置为零,它将继续并返回一个空数组。如果传入5,则数组中没有5个元素,因此在计算zero参数之前返回nil。代码在第944行
我认为这是一个bug,或者至少是不可预测的,而不是“最小惊喜原则”。当我有几分钟的时间时,我将至少向ruby core提交一个失败的测试补丁 这是有道理的
您需要能够指定给这些切片,以便它们的定义方式能够使字符串的开头和结尾具有有效的零长度表达式
array[4, 0] = :sandwich
array[0, 0] = :crunchy
=> [:crunchy, :peanut, :butter, :and, :jelly, :sandwich]
由Jim Weirich提供的解释 考虑这一点的一种方式是,指数位置4位于最边缘 数组的一部分。当你要一片面包时,你会尽可能多地退回 左边的数组。因此考虑数组[2,10],数组[3,10]和 数组[4,10]。。。每一个都返回数据结尾的剩余位 数组:分别为2个元素、1个元素和0个元素。然而 位置5明显位于阵列外部,而不是边缘,因此 数组[5,10]返回nil
当您考虑数组切片可以是一个有效的LValk时,这是有意义的,而不仅仅是rValue:
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]
如果array[4,0]
返回的是nil
而不是[]
,则这是不可能的。但是,array[5,0]
返回nil
,因为它超出了范围(在4元素数组的第4个元素之后插入有意义,但在4元素数组的第5个元素之后插入没有意义)
将切片语法
array[x,y]
解读为“在array
中的x
元素之后开始,最多选择y
元素”。只有当数组
至少包含x
元素时,这才有意义。考虑以下数组:
>> array=["a","b","c"]
=> ["a", "b", "c"]
通过将项目分配给[0,0],可以将其插入数组的开头(head)。要将元素置于“a”
和“b”
之间,请使用a[1,0]
。基本上,在符号a[i,n]
中,i
表示索引,而n
表示许多元素。当n=0
时,它定义数组元素之间的位置
现在,如果考虑数组的结尾,如何使用上面描述的符号将项附加到其结尾?简单地说,将值分配给a[3,0]
。这是数组的尾部
因此,如果您尝试访问a[3,0]
处的元素,您将获得[]
。在这种情况下,您仍然在数组的范围内。但是如果您尝试访问a[4,0]
,您将得到nil
作为返回值,因为您不再在数组的范围内
更多信息请访问。我发现加里·赖特的解释也很有帮助。 加里·赖特的答案是- 文件当然可以更清楚,但实际行为
if (beg > RARRAY_LEN(ary)) return Qnil;
array[4, 0] = :sandwich
array[0, 0] = :crunchy
=> [:crunchy, :peanut, :butter, :and, :jelly, :sandwich]
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]
>> array=["a","b","c"]
=> ["a", "b", "c"]
-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
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
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.
1189: if (offset < 0 || len <= offset) {
1190: return Qnil;
1191: }
1208: long alen = RARRAY_LEN(ary);
1209:
1210: if (beg > alen) return Qnil;
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);