Ruby中的数组切片:不合逻辑行为的解释(取自Rubykoans.com)

Pas*_*cke 228 ruby arrays

我正在参加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...
Run Code Online (Sandbox Code Playgroud)

那么为什么array[5,0]不等于array[4,0]?是否有任何理由为什么当你开始在(长+ 1)阵列切片表现这种怪异的位置?

Ama*_*dan 179

切片和索引是两种不同的操作,并且推断一方的行为是您的问题所在.

slice中的第一个参数不是元素,而是元素之间的位置,定义跨度(而不是元素本身):

  :peanut   :butter   :and   :jelly
0         1         2      3        4
Run Code Online (Sandbox Code Playgroud)

4仍在阵列内,只是勉强; 如果你请求0个元素,你得到数组的空端.但是没有索引5,所以你不能从那里切片.

当你做索引(比如array[4])时,你指的是元素本身,所以索引只从0到3.

  • 以下是此行为的理由:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/380637 (18认同)
  • 也被称为"栅栏发布".第五个fence-post(id 4)存在,但第五个元素不存在.切片是一个fence-post操作,索引是一个元素操作. (17认同)
  • 一个很好的猜测,除非这是由源支持.不是很讽刺,我会对一个链接感兴趣,如果只是为了解释像OP和其他评论者那样的"为什么".除了Array [4]为零之外,你的图表才有意义.数组[3]是:果冻.我希望Array [4,N]为零,但它就像OP所说的那样[].如果它是一个地方,那是一个相当无用的地方,因为Array [4,-1]是零.因此你无法对Array [4]做任何事情. (8认同)
  • @squarism我刚收到Charles Oliver Nutter(Twitter上的@headius)的确认,这是正确的解释.他是一个重要的JRuby开发者,所以我认为他的话非常权威. (5认同)
  • 正确的解释.关于ruby-core的类似讨论:http://redmine.ruby-lang.org/issues/4245,http://redmine.ruby-lang.org/issues/4541 (4认同)
  • 我在这里找到了更详细的解释(http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/380636 - 这讨论了字符串,但我认为它是同一个问题).基本上,范围围栏发布允许在两个字符之间插入文本.如果范围使用字符索引,则这是不可能的.由于范围可以指定最后一个字符后面的空格,因此可以附加到一个字符串(对于一个4个字符的字符串):str [4,0] ="appendme".这不是我习惯的,但现在我知道我喜欢它的原因. (2认同)
  • 几个月后,在学习Python时,我遇到了一个有用的图表来说明这个概念.它适用于Python,但您可能会发现在Ruby中为自己制作类似的图表很有帮助,因为概念是相同的.在页面中搜索"Python索引和切片:"http://wiki.python.org/moin/MovingToPythonFromOtherLanguages (2认同)
  • @Manur就像我说的那样,我只是觉得人们可能会觉得将类似的图表制作成我所链接的参考图表会很有帮助.围栏发布的总体概念是这个问题的答案背后的"原因",我认为图表格式可能有助于把握它.这是一个小小的贡献,不值得我们讨论它的空间. (2认同)

Jed*_*der 27

这与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
Run Code Online (Sandbox Code Playgroud)

这告诉我,如果你给出一个超出范围的开头,它将返回nil,因此在你的例子中array[4,0]要求存在的第四个元素,但要求返回一个零元素的数组.虽然array[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);
}
Run Code Online (Sandbox Code Playgroud)

如果你查看定义方法的array.crb_ary_subseq,你会看到如果长度超出范围,它返回nil,而不是索引:

if (beg > RARRAY_LEN(ary)) return Qnil;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,这是传入4时发生的情况,它检查有4个元素,因此不会触发nil返回.然后,如果第二个arg设置为零,它将继续并返回一个空数组.如果传入5,则数组中没有5个元素,因此在计算零arg之前返回nil.这里的代码是944行.

我认为这是一个错误,或者至少是不可预测的,而不是"最少的惊喜原则".当我得到几分钟时,我将至少向ruby核心提交一个失败的测试补丁.

  • 真是莫名其妙的行为 (3认同)
  • 但是......由数组[4,0]中的4指示的元素也不存在...... - 因为它实际上是5元素(基于0的计数,参见示例).所以它也是出界的. (2认同)

Mat*_*chu 23

至少要注意行为是一致的.从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] => []
Run Code Online (Sandbox Code Playgroud)

[4,0],我们捕获数组的结尾.我实际上觉得它很奇怪,就模式中的美感而言,如果最后一个返回nil.由于这样的上下文,4第一个参数是可接受的选项,因此可以返回空数组.但是,一旦我们达到5级以上,该方法很可能会立即完全脱离界限.


Fra*_*rba 12

当您考虑时,这是有意义的,而数组切片可以是有效的左值,而不仅仅是右值:

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]
Run Code Online (Sandbox Code Playgroud)

如果array[4,0]返回nil而不是,那将无法实现[].但是,array[5,0]返回nil因为它超出了界限(在4元素数组的第4个元素之后插入是有意义的,但是在4个元素数组的第5个元素之后插入不是).

将切片语法读array[x,y]作"从x元素开始array,选择y元素".只有array至少有x元素才有意义.


Dig*_*oss 11

确实有意义

您需要能够分配给这些切片,因此它们的定义方式使得字符串的开头和结尾具有有效的零长度表达式.

array[4, 0] = :sandwich
array[0, 0] = :crunchy
=> [:crunchy, :peanut, :butter, :and, :jelly, :sandwich]
Run Code Online (Sandbox Code Playgroud)


Mar*_*off 8

我同意这似乎是一种奇怪的行为,但即使是官方文档也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]               #=> []
Run Code Online (Sandbox Code Playgroud)

不幸的是,即使他们的描述Array#slice似乎没有提供任何关于它为什么这样工作的见解:

元素引用 - 返回索引处的元素,或返回从start开始 并继续为length 元素的子数组,或返回由range指定的子数组.负索引从数组末尾向后计数(-1是最后一个元素).如果索引(或起始索引)超出范围,则返回nil.


vim*_*vim 8

我发现Gary Wright的解释也很有帮助. http://www.ruby-forum.com/topic/1393096#990065

Gary Wright的回答是 -

http://www.ruby-doc.org/core/classes/Array.html

文档当然可以更清楚,但实际行为是自我一致和有用的.注意:我假设1.9.X版本的String.

它有助于以下列方式考虑编号:

  -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
Run Code Online (Sandbox Code Playgroud)

常见(且可理解的)错误也是假设单个参数索引的语义与两个参数场景(或范围)中第一个参数的语义相同 .它们在实践中并不相同,文档也没有反映出这一点.虽然错误肯定在文档中,而不是在实现中:

单个参数:索引表示字符串中的单个字符位置.结果是在索引处找到的单个字符串或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
Run Code Online (Sandbox Code Playgroud)

两个整数参数:参数标识要提取或替换的字符串的一部分.特别地,还可以识别字符串的零宽度部分,使得可以在包括在字符串的前端或末尾的现有字符之前或之后插入文本.在这种情况下,第一个参数并不能识别一个字符的位置,而是识别为示出在图中上面的字符之间的空间.第二个参数是长度,可以是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"
Run Code Online (Sandbox Code Playgroud)

范围的行为非常有趣.当提供两个参数时,起点与第一个参数相同(如上所述),但是范围的结束点可以是与单个索引一样的"字符位置",也可以是与两个整数参数一样的"边缘位置".差异取决于是使用双点范围还是三点范围:

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.
Run Code Online (Sandbox Code Playgroud)

如果您回顾这些示例并坚持并使用单个索引语义进行双重或范围索引示例,您将会感到困惑.你必须使用我在ascii图中显示的备用编号来模拟实际行为.

  • 你能包括那个主题的主要想法吗?(如果链接有一天变得无效) (3认同)

suv*_*kar 7

Jim Weirich提供的解释

考虑它的一种方法是索引位置4位于数组的最边缘.在请求切片时,您将返回剩余的数组.因此,考虑数组[2,10],数组[3,10]和数组[4,10] ...每个返回数组末尾的剩余位:分别为2个元素,1个元素和0个元素.但是,位置5显然数组外部而不在边缘,因此数组[5,10]返回nil.


小智 6

考虑以下数组:

>> array=["a","b","c"]
=> ["a", "b", "c"]
Run Code Online (Sandbox Code Playgroud)

您可以通过将项目分配给数组的开始(头部)来插入项目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/上阅读更多相关信息.