Estaba pasando por los ejercicios en Ruby Koans y me llamó la atención la siguiente peculiaridad de Ruby que encontré realmente inexplicable:
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...
Entonces, ¿por qué array[5,0]
no es igual a array[4,0]
? ¿Hay alguna razón por la matriz de corte en rodajas se comporta de esta extraña cuando se inicia en el (largo + 1) ª posición ??
Respuestas:
Rebanar e indexar son dos operaciones diferentes, y el problema radica en inferir el comportamiento de uno del otro.
El primer argumento en el segmento no identifica el elemento sino los lugares entre los elementos, definiendo los tramos (y no los elementos en sí):
4 todavía está dentro de la matriz, apenas; si solicita 0 elementos, obtiene el extremo vacío de la matriz. Pero no hay un índice 5, por lo que no puede cortar desde allí.
Cuando indexas (like
array[4]
), estás apuntando a los elementos mismos, por lo que los índices solo van de 0 a 3.fuente
esto tiene que ver con el hecho de que slice devuelve una matriz, documentación fuente relevante de Array # slice:
lo que me sugiere que si da el inicio que está fuera de los límites, devolverá nulo, por lo tanto, en su ejemplo,
array[4,0]
solicita el cuarto elemento que existe, pero pide que devuelva una matriz de cero elementos. Mientras quearray[5,0]
pide un índice fuera de límites, devuelve nulo. Esto quizás tenga más sentido si recuerda que el método de división está devolviendo una nueva matriz, sin alterar la estructura de datos original.EDITAR:
Después de revisar los comentarios, decidí editar esta respuesta. Slice llama al siguiente fragmento de código cuando el valor arg es dos:
si mira en la
array.c
clase donderb_ary_subseq
se define el método, verá que devuelve nulo si la longitud está fuera de los límites, no el índice:En este caso, esto es lo que sucede cuando se pasa 4, comprueba que hay 4 elementos y, por lo tanto, no activa el retorno nulo. Luego continúa y devuelve una matriz vacía si el segundo argumento se establece en cero. mientras que si se pasa 5, no hay 5 elementos en la matriz, por lo que devuelve cero antes de evaluar el argumento cero. código aquí en la línea 944.
Creo que esto es un error, o al menos impredecible y no el "Principio de la menor sorpresa". Cuando tenga unos minutos, al menos enviaré un parche de prueba fallido a ruby core.
fuente
Al menos tenga en cuenta que el comportamiento es consistente. A partir de las 5, todo actúa igual; la rareza solo ocurre en
[4,N]
.Tal vez este patrón ayuda, o tal vez estoy cansado y no ayuda en absoluto.
En
[4,0]
, tomamos el final de la matriz. En realidad, me parecería bastante extraño, en lo que respecta a la belleza en los patrones, si el último regresaranil
. Debido a un contexto como este,4
es una opción aceptable para el primer parámetro para que se pueda devolver la matriz vacía. Sin embargo, una vez que alcanzamos 5 y más, el método probablemente salga inmediatamente por naturaleza de estar totalmente y completamente fuera de los límites.fuente
Esto tiene sentido cuando considera que un segmento de matriz puede ser un valor l válido, no solo un valor r:
Esto no sería posible si se
array[4,0]
devuelve ennil
lugar de[]
. Sin embargo,array[5,0]
regresanil
porque está fuera de los límites (la inserción después del cuarto elemento de una matriz de 4 elementos es significativa, pero la inserción después del quinto elemento de una matriz de 4 elementos no lo es).Lea la sintaxis de corte
array[x,y]
como "comenzando después de losx
elementosarray
, seleccione hastay
elementos". Esto solo tiene sentido siarray
tiene al menosx
elementos.fuente
Esto tiene sentido
Debe poder asignar esas secciones, de modo que se definan de tal manera que el principio y el final de la cadena tengan expresiones de longitud cero que funcionen.
fuente
array[5,0]=:foo # array is now [:peanut, :butter, :and, :jelly, nil, :foo]
[26] pry(main)> array[4,5] = [:love, :hope, :peace] => [:peanut, :butter, :and, :jelly, :love, :hope, :peace]
array = [:a, :b, :c, :d, :e]; array[1,2] = :x, :x; array => [:a, :x, :x, :d, :e]
También encontré muy útil la explicación de Gary Wright. http://www.ruby-forum.com/topic/1393096#990065
La respuesta de Gary Wright es:
http://www.ruby-doc.org/core/classes/Array.html
Los documentos ciertamente podrían ser más claros, pero el comportamiento real es coherente y útil. Nota: estoy asumiendo la versión 1.9.X de String.
Ayuda a considerar la numeración de la siguiente manera:
El error común (y comprensible) es también asumir que la semántica del índice de argumento único es la misma que la semántica del primer argumento en el escenario (o rango) de dos argumentos. No son lo mismo en la práctica y la documentación no refleja esto. Sin embargo, el error definitivamente está en la documentación y no en la implementación:
argumento único: el índice representa una posición de un solo carácter dentro de la cadena. El resultado es la cadena de un solo carácter que se encuentra en el índice o nula porque no hay ningún carácter en el índice dado.
dos argumentos enteros: los argumentos identifican una porción de la cadena para extraer o reemplazar. En particular, las partes de la cadena de ancho cero también se pueden identificar para que el texto se pueda insertar antes o después de los caracteres existentes, incluso en el frente o al final de la cadena. En este caso, el primer argumento no identifica una posición de carácter, sino que identifica el espacio entre caracteres como se muestra en el diagrama anterior. El segundo argumento es la longitud, que puede ser 0.
El comportamiento de un rango es bastante interesante. El punto de partida es el mismo que el primer argumento cuando se proporcionan dos argumentos (como se describió anteriormente), pero el punto final del rango puede ser la 'posición de caracteres' como con indexación simple o la "posición de borde" como con dos argumentos enteros. La diferencia está determinada por si se utiliza el rango de punto doble o el rango de punto triple:
Si revisa estos ejemplos e insiste y usa la semántica de índice único para los ejemplos de indexación doble o de rango, se confundirá. Tienes que usar la numeración alternativa que muestro en el diagrama ASCII para modelar el comportamiento real.
fuente
Estoy de acuerdo en que esto parece un comportamiento extraño, pero incluso la documentación oficial
Array#slice
muestra el mismo comportamiento que en su ejemplo, en los "casos especiales" a continuación:Desafortunadamente, incluso su descripción de
Array#slice
no parece ofrecer ninguna idea de por qué funciona de esta manera:fuente
Una explicación proporcionada por Jim Weirich
fuente
Considere la siguiente matriz:
Puede insertar un elemento al principio (encabezado) de la matriz asignándolo a
a[0,0]
. Para poner el elemento entre"a"
y"b"
, usea[1,0]
. Básicamente, en la notacióna[i,n]
,i
representa un índice yn
una serie de elementos. Cuandon=0
, define una posición entre los elementos de la matriz.Ahora, si piensa en el final de la matriz, ¿cómo puede agregar un elemento a su final utilizando la notación descrita anteriormente? Simple, asigne el valor a
a[3,0]
. Esta es la cola de la matriz.Entonces, si intenta acceder al elemento en
a[3,0]
, obtendrá[]
. En este caso, todavía está en el rango de la matriz. Pero si intenta accedera[4,0]
, obtendránil
como valor de retorno, ya que ya no está dentro del rango de la matriz.Lea más sobre esto en http://mybrainstormings.wordpress.com/2012/09/10/arrays-in-ruby/ .
fuente
tl; dr: en el código fuente, se invocan
array.c
diferentes funciones dependiendo de si pasa 1 o 2 argumentos para dar comoArray#slice
resultado valores de retorno inesperados.(En primer lugar, me gustaría señalar que no codifico en C, pero he estado usando Ruby durante años. Entonces, si no está familiarizado con C, pero se toma unos minutos para familiarizarse con los conceptos básicos de funciones y variables realmente no es tan difícil seguir el código fuente de Ruby, como se demuestra a continuación. Esta respuesta se basa en Ruby v2.3, pero es más o menos lo mismo de vuelta a v1.9.)
Escenario 1
array.length == 4; array.slice(4) #=> nil
Si observa el código fuente de
Array#slice
(rb_ary_aref
), verá que cuando solo se pasa un argumento ( líneas 1277-1289 ),rb_ary_entry
se llama y pasa el valor del índice (que puede ser positivo o negativo).rb_ary_entry
luego calcula la posición del elemento solicitado desde el comienzo de la matriz (en otras palabras, si se pasa un índice negativo, calcula el equivalente positivo) y luego llamarb_ary_elt
para obtener el elemento solicitado.Como se esperaba,
rb_ary_elt
regresanil
cuando la longitud de la matrizlen
es menor o igual que el índice (aquí llamadooffset
).Escenario # 2
array.length == 4; array.slice(4, 0) #=> []
Sin embargo, cuando se pasan 2 argumentos (es decir , se llama al índice inicial
beg
y la longitud del segmentolen
)rb_ary_subseq
.En
rb_ary_subseq
, si el índice inicialbeg
es mayor que la longitud de la matrizalen
,nil
se devuelve:De lo contrario,
len
se calcula la longitud del segmento resultante y, si se determina que es cero, se devuelve una matriz vacía:Entonces, dado que el índice inicial de 4 no es mayor que
array.length
, se devuelve una matriz vacía en lugar delnil
valor que cabría esperar.Pregunta contestada?
Si la pregunta real aquí no es "¿Qué código hace que esto suceda?", Sino más bien, "¿Por qué Matz lo hizo de esta manera?", Bueno, solo tendrás que comprarle una taza de café en el próximo RubyConf y preguntarle.
fuente