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]
나는 이것이 이상한 행동처럼 보이지만 공식 문서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을 반환합니다.
게리 라이트의 설명도 도움이되었습니다. 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 인덱싱 예제에 단일 인덱스 의미론을 사용하고 고집한다면 혼란 스러울 것입니다. 실제 동작을 모델링하기 위해 아스키 다이어그램에 표시되는 대체 번호 매기기를 사용해야합니다.
Jim Weirich가 제공 한 설명
그것에 대해 생각하는 한 가지 방법은 인덱스 위치 4가 배열의 가장 가장자리에 있다는 것입니다. 슬라이스를 요청할 때 남아있는 많은 배열을 반환합니다. 따라서 array [2,10], array [3,10] 및 array [4,10] ... 각각 배열 끝의 나머지 비트를 각각 2 요소, 1 요소 및 0 요소로 반환합니다. 그러나 위치 5는 명확하게 배열 외부 에 있고 가장자리가 아니므로 배열 [5,10]은 nil을 반환합니다.
다음 배열을 고려하십시오.
>> array=["a","b","c"]
=> ["a", "b", "c"]
에 할당하여 항목을 배열의 시작 (머리)에 삽입 할 수 있습니다 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/ 에서 확인 하십시오 .
tl; dr :의 소스 코드에서 예기치 않은 리턴 값 array.c
을 Array#slice
초래하는 1 개 또는 2 개의 인수를 전달하는지에 따라 다른 함수가 호출 됩니다.
(먼저 C로 코드를 작성하지는 않지만 몇 년 동안 루비를 사용하고 있음을 지적하고 싶습니다. 따라서 C에 익숙하지 않지만 기본 사항에 익숙해지는 데 몇 분이 걸립니다. 함수와 변수에 대해서는 아래에 설명 된 것처럼 Ruby 소스 코드를 따르는 것이 어렵지 않습니다.이 답변은 Ruby v2.3을 기반으로하지만 v1.9와 거의 동일합니다.)
시나리오 # 1
array.length == 4; array.slice(4) #=> nil
Array#slice
( rb_ary_aref
) 에 대한 소스 코드를 보면 , 하나의 인수 만 전달되면 ( 1277-1289 행 ) rb_ary_entry
인덱스 값 (양수 또는 음수)을 전달하여 호출됩니다.
rb_ary_entry
그런 다음 배열의 시작 부분에서 요청 된 요소의 위치를 계산합니다 (즉, 음의 인덱스가 전달되면 양의 값을 계산합니다) rb_ary_elt
. 그러면 요청 된 요소를 가져 오기 위해 호출 합니다.
예상대로 rb_ary_elt
복귀 nil
배열의 길이가 될 때 len
이다 동일보다 작거나 (여기서 불리는 인덱스 offset
).
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
가 계산되고 0으로 결정되면 빈 배열이 반환됩니다.
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.length
대신 빈 배열이 리턴됩니다 nil
.
질문이 있습니까?
여기서 실제 질문이 "어떤 코드로 인해 이런 일이 발생합니까?"가 아니라 "왜 Matz가 이런 식으로 작업 했습니까?"가 아니라면 다음 RubyConf에서 커피 한 잔을 사야합니다. 물어.
'Programing' 카테고리의 다른 글
Android 대화 상자 Fragment vs Dialog (0) | 2020.04.17 |
---|---|
왜리스트에 사전과 같은 안전한 "get"메소드가 없는가? (0) | 2020.04.17 |
C ++에 헤더 파일을 포함하는 동안 꺾쇠 괄호 <>와 큰 따옴표 ""의 차이점은 무엇입니까? (0) | 2020.04.17 |
url ()의 값을 인용하는 것이 정말로 필요한가? (0) | 2020.04.17 |
MySQL은 쿼리에서 ORDER BY 및 LIMIT를 어떻게 처리합니까? (0) | 2020.04.17 |