Распределение массивов в 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 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
Ответы
Ответ 1
Нарезка и индексирование - это две разные операции, и выведение поведения одного из другого - вот где ваша проблема.
Первый аргумент в срезе идентифицирует не элемент, а места между элементами, определяющие пролеты (а не сами элементы):
:peanut :butter :and :jelly
0 1 2 3 4
4 все еще находится внутри массива, едва ли; если вы запросите 0 элементов, вы получите пустой конец массива. Но нет индекса 5, поэтому вы не можете нарезать оттуда.
Когда вы индексируете (например, array[4]
), вы указываете сами элементы, поэтому индексы идут только от 0 до 3.
Ответ 2
Это связано с тем, что срез возвращает массив, соответствующую исходную документацию из массива # 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]
запрашивает 4-й элемент, который существует, но просит вернуть массив из нулевых элементов. Пока array[5,0]
запрашивает индекс за пределами, поэтому он возвращает nil. Возможно, это имеет смысл, если вы помните, что метод slice возвращает новый массив, не изменяя исходную структуру данных.
EDIT:
После просмотра комментариев я решил отредактировать этот ответ. Slice вызывает следующий фрагмент кода, когда значение arg равно двум:
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 установлен на ноль. в то время как если 5 передано, в массиве не содержится 5 элементов, поэтому он возвращает nil до вычисления нуля arg. код здесь в строке 944.
Я считаю, что это ошибка или, по крайней мере, непредсказуемая, а не "Принцип наименьшего сюрприза". Когда я получу несколько минут, я по крайней мере отправлю неудачный патч для проверки на рубиновый ядро.
Ответ 3
По крайней мере, обратите внимание, что поведение согласовано. Начиная с 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 и выше, метод, вероятно, немедленно выйдет из-за того, что он полностью и полностью выходит за пределы.
Ответ 4
Это имеет смысл, если вы считаете, что срез массива может быть допустимым lvalue, а не только 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 элементов имеет смысл, но вставка после 5-го элемента массива из 4 элементов не является).
Прочитайте синтаксис фрагмента array[x,y]
как "начиная с x
элементов в array
, выберите до y
элементов". Это имеет смысл только в том случае, если array
имеет не менее x
элементов.
Ответ 5
Это имеет смысл
Вы должны иметь возможность назначать эти срезы, поэтому они определяются таким образом, что начало и конец строки имеют рабочие выражения с нулевой длиной.
array[4, 0] = :sandwich
array[0, 0] = :crunchy
=> [:crunchy, :peanut, :butter, :and, :jelly, :sandwich]
Ответ 6
Я согласен, что это похоже на странное поведение, но даже официальная документация на 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
, похоже, не дает никакого представления о том, почему оно работает таким образом:
Элемент Reference - возвращает элемент по индексу или возвращает подмассив, начинающийся с начала и продолжающийся для элементов длины, или возвращает субарей, заданный диапазоном. Отрицательные индексы отсчитываются назад от конца массива (-1 - последний элемент). Возвращает nil, если индекс (или начальный индекс) выходит за пределы диапазона.
Ответ 7
Я нашел объяснение Гари Райт очень полезным.
http://www.ruby-forum.com/topic/1393096#990065
Ответ Гэри Райт -
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
Общая (и понятная) ошибка слишком предполагает, что семантика
индекса одного аргумента совпадают с семантикой
первый аргумент в сценарии двух аргументов (или диапазона). Они не
то же самое на практике, и документация не отражает этого.
Ошибка, хотя определенно находится в документации, а не в
реализация:
единственный аргумент: индекс представляет собой одиночную позицию символа
внутри строки. Результатом является либо одиночная символьная строка
найденный в индексе или ноль, потому что нет символа в данном
индекс.
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
два целых аргумента: аргументы идентифицируют часть строки для
извлечь или заменить. В частности, части нулевой ширины строки
также могут быть идентифицированы так, чтобы текст мог быть вставлен до или после
существующие символы, включая фронт или конец строки. В этом
case, первый аргумент не идентифицирует позицию символа, но
вместо этого определяет пространство между символами, как показано на диаграмме
выше. Второй аргумент - длина, которая может быть равна 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.
Если вы вернетесь к этим примерам и настаиваете и используете сингл
семантика индекса для двойных или диапазонных индексирующих примеров вы просто
смутиться. Вы должны использовать альтернативную нумерацию, которую я показываю в
ascii для моделирования фактического поведения.
Ответ 8
Объяснение, данное Джим Вейрихом
Один из способов подумать о том, что позиция индекса 4 находится на самом краю массива. Когда вы запрашиваете фрагмент, вы возвращаете столько массив, который оставлен. Поэтому рассмотрим массив [2,10], массив [3,10] и array [4,10]... каждый возвращает оставшиеся биты конца array: 2 элемента, 1 элемент и 0 элементов соответственно. Однако, позиция 5 явно находится вне массива, а не на краю, поэтому массив [5,10] возвращает ноль.
Ответ 9
Рассмотрим следующий массив:
>> 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/.