[arrays] Ruby에서의 배열 슬라이싱 : 비논리적 행동에 대한 설명 (Rubykoans.com에서 가져옴)

나는 Ruby Koans 에서 연습을 했는데 정말 설명 할 수없는 다음의 루비 문제에 부딪쳤다 .

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...

그렇다면 왜 array[5,0]같지 array[4,0]않습니까? (길이 + 1) 번째 위치 에서 시작할 때 배열 슬라이싱이 이상하게 동작하는 이유가 있습니까 ?



답변

슬라이싱 및 인덱싱은 서로 다른 두 가지 작업이며 서로의 동작을 유추하는 것은 문제가있는 부분입니다.

slice의 첫 번째 인수는 요소가 아니라 요소 사이의 위치를 ​​식별하여 범위를 정의하고 요소 자체는 정의하지 않습니다.

  :peanut   :butter   :and   :jelly
0         1         2      3        4

4는 여전히 배열 안에 있습니다. 0 개의 요소를 요청하면 배열의 빈 끝이 나타납니다. 그러나 인덱스 5가 없으므로 거기서 슬라이스 할 수 없습니다.

index (와 같은 array[4]) 를 수행하면 요소 자체를 가리 키므로 인덱스는 0에서 3으로 만 이동합니다.


답변

이것은 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을 반환하므로 예제에서 array[4,0]존재하는 네 번째 요소를 요청하지만 0 개의 요소 배열을 반환하도록 요청합니다. 동안은 array[5,0]이 전무 반환 있도록 범위를 벗어 인덱스를 요청합니다. 슬라이스 메서드가 원래 데이터 구조를 변경하지 않고 배열을 반환한다는 것을 기억하면 더 의미가 있습니다 .

편집하다:

의견을 검토 한 후이 답변을 편집하기로 결정했습니다. 슬라이스 값이 2 일 때 슬라이스는 다음 코드 스 니펫을 호출합니다 .

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);
}

메소드가 정의 된 array.c클래스 를 보면 rb_ary_subseq길이가 색인이 아닌 범위를 벗어나면 nil을 반환한다는 것을 알 수 있습니다.

if (beg > RARRAY_LEN(ary)) return Qnil;

이 경우 4가 전달 될 때 발생하는 상황으로, 4 개의 요소가 있는지 확인하여 nil 리턴을 트리거하지 않습니다. 그런 다음 두 번째 arg가 0으로 설정되면 계속 진행하여 빈 배열을 반환합니다. 5가 전달되면 배열에 5 개의 요소가 없으므로 0 인수가 평가되기 전에 nil을 리턴합니다. 여기 944 행에 코드를 작성 하십시오 .

나는 이것이 버그이거나 적어도 예측할 수 없으며 ‘최소한의 서프라이즈 원칙’이 아니라고 생각합니다. 몇 분이 지나면 최소한 실패한 테스트 패치를 루비 코어에 제출합니다.


답변

최소한 동작은 일관 적입니다. 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를 넘으면, 그 방법은 완전히 그리고 완전히 범위를 벗어난 성질로 즉시 빠져 나올 것입니다.


답변

이것은 배열 슬라이스가 rvalue가 아닌 유효한 lvalue가 될 수 있다고 생각할 때 의미가 있습니다.

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]반환 하면 불가능합니다 . 그러나 범위를 벗어 났으므로 리턴 합니다 (4 요소 배열의 4 번째 요소 뒤에 삽입하는 것은 의미가 있지만 4 요소 배열의 5 번째 요소 뒤에 삽입하는 것은 의미가 없습니다).nil[]array[5,0]nil

array[x,y]x에서 요소 뒤에서 시작 array, 최대 y요소 선택 “으로 슬라이스 구문 을 읽 습니다 . 이것은 array적어도 x요소 가 있는 경우에만 의미가 있습니다 .


답변

수행 메이크업 감각을

해당 슬라이스에 할당 할 수 있어야하므로 문자열의 시작과 끝이 길이가 0 인 식으로 정의됩니다.

array[4, 0] = :sandwich
array[0, 0] = :crunchy
=> [:crunchy, :peanut, :butter, :and, :jelly, :sandwich]


답변

게리 라이트의 설명도 도움이되었습니다.
http://www.ruby-forum.com/topic/1393096#990065

게리 라이트의 대답은-

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

문서는 확실히 더 명확 할 수 있지만 실제 동작은 일관되고 유용합니다. 참고 : String 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 인 부분을 식별하여 문자열의 앞이나 끝을 포함하여 기존 문자 앞이나 뒤에 텍스트를 삽입 할 수 있습니다. 이 경우 첫 번째 인수는 문자 위치를 식별 하지 않고 위의 다이어그램에 표시된 것처럼 문자 사이의 공백을 식별합니다. 두 번째 인수는 길이이며 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.

이 예제를 다시 살펴보고 double 또는 range 인덱싱 예제에 단일 인덱스 의미론을 사용하고 고집한다면 혼란 스러울 것입니다. 실제 동작을 모델링하기 위해 아스키 다이어그램에 표시되는 대체 번호 매기기를 사용해야합니다.


답변

나는 이것이 이상한 행동처럼 보이지만 공식 문서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 대한 통찰력을 제공하지 못하는 것 같습니다 .

요소에있는 요소를 참조-반환 인덱스 , 또는 반환 부분 배열에서 시작 시작 과에 대한 지속적인 길이 요소, 또는 리턴으로 지정된 부분 배열 범위 . 음수 인덱스는 배열의 끝에서 뒤로 계산됩니다 (-1은 마지막 요소 임). 인덱스 (또는 시작 인덱스)가 범위를 벗어나면 nil을 반환합니다.