programing

루비의 배열 슬라이싱: 비논리적인 행동에 대한 설명(Rubykoans.com 에서 제공)

stoneblock 2023. 5. 29. 09:15

루비의 배열 슬라이싱: 비논리적인 행동에 대한 설명(Rubykoans.com 에서 제공)

저는 루비 코안스에서 연습을 하고 있었는데, 제가 정말 설명할 수 없는 루비 기괴함에 놀랐습니다.

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)th 위치에서 시작할 때 어레이 슬라이싱이 이렇게 이상하게 동작하는 이유가 있습니까?

슬라이싱과 인덱싱은 서로 다른 작업이며, 이 작업의 동작을 서로 유추하는 것이 문제입니다.

슬라이스의 첫 번째 인수는 요소가 아니라 요소 사이의 위치를 식별하며 범위를 정의합니다(요소 자체는 아님).

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

4는 여전히 배열 내에 있으며, 겨우 0개의 요소를 요청하면 배열의 빈 끝을 얻을 수 있습니다.하지만 지수 5가 없어서 거기서부터 자를 수 없습니다.

을 할 예:array[4]자체를 까지밖에 가지 않습니다.), 자에까체가원리로지으므있수고소키를 0서지 3다표니시됩만는▁),까다표에니▁you▁them시.

이는 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

그것은 만약 당신이 경계를 벗어난 시작을 한다면, 그것은 0으로 돌아갈 것이고, 따라서 당신의 예에서.array[4,0]에서는 존재하는 4번째 요소를 요청하지만 0개의 요소 배열을 반환하도록 요청합니다.하는 동안에array[5,0]인덱스가 범위를 벗어났으므로 인덱스가 0을 반환합니다.슬라이스 방법이 원래 데이터 구조를 변경하는 것이 아니라 배열을 반환하는 것임을 기억하는 경우 이 방법이 더 타당할 수 있습니다.

편집:

댓글을 검토한 후 이 답변을 편집하기로 했습니다.Slice는 arg 값이 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메소드가 정의되면 길이가 인덱스가 아닌 범위를 벗어나면 메소드가 0으로 반환됩니다.

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

이 경우 4가 전달되면 4개의 요소가 있는지 확인하여 0 반환을 트리거하지 않습니다.그런 다음 두 번째 arg가 0으로 설정된 경우 계속되고 빈 배열을 반환합니다.5를 전달하면 배열에 5개의 요소가 없으므로 0 인수를 평가하기 전에 0을 반환합니다.여기 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 이상이 되면 그 방법은 완전히 그리고 완전히 범위를 벗어났기 때문에 즉시 종료될 가능성이 높습니다.

배열 슬라이스가 r 값뿐만 아니라 유효한 l 값이 될 수 있다는 점을 고려할 때 이는 타당합니다.

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]에 시작"으로xarray지까고다까지 합니다.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

일반적인 (그리고 이해할 수 있는) 실수는 단일 인수 인덱스의 의미가 두 인수 시나리오(또는 범위)의 첫 번째 인수의 의미와 동일하다고 너무 가정합니다.이들은 실제로 동일한 것이 아니며 문서에는 이를 반영하지 않습니다.오류는 분명히 설명서에 있지만 구현에는 없습니다.

단일 인수: 인덱스는 문자열 내의 단일 문자 위치를 나타냅니다.결과는 인덱스에서 발견된 단일 문자열이거나 지정된 인덱스에 문자가 없기 때문에 0입니다.

  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.

이러한 예를 다시 살펴보고 이중 또는 범위 인덱싱 예제에 대해 단일 인덱스 의미론을 사용한다고 주장하면 혼란이 발생할 수 있습니다.실제 동작을 모델링하려면 아스키 다이어그램에 표시된 대체 번호를 사용해야 합니다.

이 동작이 이상한 것 같다는 데 동의하지만 의 공식 문서에서도 아래 "특별한 경우"의 예와 동일한 동작을 보여줍니다.

   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은 마지막 요소).인덱스(또는 시작 인덱스)가 범위를 벗어나면 0을 반환합니다.

Jim Weirich가 제공한 설명

한 가지 방법은 인덱스 위치 4가 배열의 가장 가장자리에 있다는 것입니다.슬라이스를 요청할 때 남은 어레이의 양만큼 반환합니다.어레이[2,10], 어레이[3,10] 및 어레이[4,10]를 고려해 보십시오.각각 배열 끝 부분의 나머지 비트(각각 2개, 1개, 0개)를 반환합니다.그러나 위치 5는 분명히 배열 외부에 있고 가장자리에 있지 않으므로 배열 [5,10]은 0을 반환합니다.

다음 배열을 고려합니다.

>> array=["a","b","c"]
=> ["a", "b", "c"]

있)목 항 당 삽 수 할 니 다 입 드 을 하 여 ▁you 니 다 있 ▁an ▁by 습 헤 ▁assigning ▁to ▁insert 에 ▁it 드 ) ) ▁array head 수 ▁item 다 ▁the ▁to ▁can ▁of 음 ▁thea[0,0]요소를 중간에 배치합니다."a"그리고."b",사용하다a[1,0]기적으로라는 에서는 에서법표기본서▁basically에▁in,▁notation▁the.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/ 을 참조하십시오.

tl;dr: 의 소스 코드에서, 1 또는 2 인수를 에 전달할지에 따라 다른 함수가 호출됩니다.Array#slice예기치 않은 반환 값이 발생합니다.

(먼저, 저는 C로 코딩하지 않고 몇 년 동안 루비를 사용해 왔습니다.따라서 C에 익숙하지 않지만 함수와 변수의 기본을 익히는 데 몇 분이 걸린다면 아래에 설명된 것처럼 루비 소스 코드를 따르는 것은 정말 어렵지 않습니다.이 답변은 Ruby v2.3을 기반으로 하지만 v1.9와 다소 동일합니다.)

시나리오 #1

array.length == 4; array.slice(4) #=> nil

보면를드의 ,Array#slice() 하나의 인수만 전달되는 경우(1277-1289행) 인덱스 값(양 또는 음일 수 있음)을 전달하여 호출되는 것을 확인할 수 있습니다.rb_ary_aref

rb_ary_entry 그런 다음 배열의 시작 부분에서 요청된 요소의 위치를 계산한 다음(즉, 음수 인덱스가 전달된 경우 양의 등가물을 계산함), 요청된 요소를 가져오도록 호출합니다.

예상대로, 반환됩니다.nil가 배의길다음같을과때가이열 ▁the때같을▁oflen인덱스보다 작거나 같음(여기서 이를 가리킴offset).

1189:  if (offset < 0 || len <= offset) {
1190:    return Qnil;
1191:  } 

시나리오 #2

array.length == 4; array.slice(4, 0) #=> []

의 인수가 때 시작 인덱스는 "" " " " " " " " " " (", " " " " " )beg len)를 호출합니다.

in, 시작 인덱스인 경우beg배열 길이보다 큼alen,nil반환됨:

1208:  long alen = RARRAY_LEN(ary);
1209:
1210:  if (beg > alen) return Qnil;

않으면 가 렇지않면결슬라길의스이이과그으▁otherwise길이▁of의▁length.len되면 빈 반환됩니다.: " " " " " " 으 " 계 " 되 " 산 " 이 " 고 " 됩 " 니 " 환 " 다 " 반 " 이 " 열 " 배 " 빈 " 면 " 로 " 되 " 결 " 정 " ▁is "

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문다 입크니 지다보 다음작 지수▁so 때니다.array.length다음 대신 빈 배열이 반환됩니다.nil기대할 수 있는 가치

질문에 답했습니까?

여기서 실제 질문이 "어떤 코드가 이것을 발생시키는가?"가 아니라 "마츠가 왜 이런 식으로 했을까?"라면 다음 루비콘프에서 커피를 사주고 그에게 물어봐야 할 것입니다.

언급URL : https://stackoverflow.com/questions/3568222/array-slicing-in-ruby-explanation-for-illogical-behaviour-taken-from-rubykoans