tamaño rails objects len item array arrays ruby

arrays - rails - ruby select



Array slicing en Ruby: explicación para el comportamiento ilógico(tomado de Rubykoans.com) (10)

tl; dr: en el código fuente en array.c , se llaman diferentes funciones dependiendo de si pasa 1 o 2 argumentos en Array#slice resulta en los 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. Así que si no estás familiarizado con C, te tomas unos minutos para familiarizarte con lo básico de funciones y variables realmente no es tan difícil seguir el código fuente de Ruby, como se muestra a continuación. Esta respuesta se basa en Ruby v2.3, pero es más o menos la misma que en 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 ), se llama a rb_ary_entry , pasando el valor del índice (que puede ser positivo o negativo).

rb_ary_entry luego calcula la posición del elemento solicitado desde el principio de la matriz (en otras palabras, si se pasa un índice negativo, calcula el equivalente positivo) y luego llama a rb_ary_elt para obtener el elemento solicitado.

Como era de esperar, rb_ary_elt devuelve nil cuando la longitud del len de la matriz es menor o igual que el índice (aquí denominado offset ).

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

Escenario # 2

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

Sin embargo, cuando se pasan 2 argumentos (es decir, el inicio del índice de inicio y la longitud del segmento len ), se llama a rb_ary_subseq .

En rb_ary_subseq , si el inicio del índice inicial es mayor que el alen de la matriz, se devuelve nil :

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

De lo contrario, se calcula la longitud del segmento resultante len y, si se determina que es cero, se devuelve una matriz vacía:

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

Entonces, dado que el índice de inicio de 4 no es mayor que array.length , se array.length una matriz vacía en lugar del valor nil que uno podría esperar.

Pregunta respondida?

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á que comprarle una taza de café en el próximo RubyConf y preguntarle.

Estaba realizando los ejercicios en Ruby Koans y me sorprendió 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é la array[5,0] no es igual a la array[4,0] ? ¿Hay alguna razón por la que la división de matrices se comporte de forma extraña cuando empiezas en la posición (longitud + 1)?


Esto tiene sentido

Debe poder asignar a esos segmentos, de modo que estén definidos de tal manera que el principio y el final de la cadena tengan expresiones de longitud cero que funcionen.

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


Al menos nota que el comportamiento es consistente. De 5 en adelante todo actúa igual. la rareza solo ocurre en [4,N] .

Tal vez este patrón ayude, o tal vez estoy cansado y no ayuda en absoluto.

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] => []

En [4,0] , atrapamos el final de la matriz. De hecho, me parece bastante extraño, en lo que respecta a la belleza en los patrones, si el último regresara a nil . 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, es probable que el método salga inmediatamente por la naturaleza de estar totalmente fuera de los límites.


Considere la siguiente matriz:

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

Puede insertar un elemento al principio (encabezado) de la matriz asignándolo a a[0,0] . Para poner el elemento entre "a" y "b" , use a[1,0] . Básicamente, en la notación a[i,n] , i representa un índice n una serie de elementos. Cuando n=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 extremo utilizando la notación descrita anteriormente? Simple, asigne el valor a a[3,0] . Esta es la cola de la matriz.

Por lo tanto, si intenta acceder al elemento en a[3,0] , obtendrá [] . En este caso, usted todavía está en el rango de la matriz. Pero si intenta acceder a a[4,0] , obtendrá un valor de retorno nil , 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/ .


El corte y la indexación son dos operaciones diferentes, e inferir el comportamiento de una de la otra es donde reside el problema.

El primer argumento de slice no identifica el elemento sino los lugares entre los elementos, definiendo los intervalos (y no los elementos en sí mismos):

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

4 está todavía 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 se puede cortar desde allí.

Cuando haces un índice (como array[4] ), estás apuntando a los elementos en sí mismos, por lo que los índices solo van de 0 a 3.


Esto tiene sentido cuando se considera que un segmento de matriz puede ser un valor válido, no solo un valor de r:

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]

Esto no sería posible si la array[4,0] devolviera nil lugar de [] . Sin embargo, la array[5,0] devuelve nil porque está fuera de 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 array[x,y] sintaxis de array[x,y] como "comenzando después de x elementos en la array , seleccione hasta y elementos". Esto solo es significativo si la array tiene al menos x elementos.


Estoy de acuerdo en que esto parece un comportamiento extraño, pero incluso la documentación oficial en la Array#slice demuestra el mismo comportamiento que en su ejemplo, en los "casos especiales" a continuación:

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] #=> []

Desafortunadamente, incluso su descripción de Array#slice no parece ofrecer ninguna idea de por qué funciona de esta manera:

Referencia de elemento: devuelve el elemento en el índice , o devuelve un subarreglo que comienza en el inicio y continúa para elementos de longitud , o devuelve un subarreglo especificado por rango . Los índices negativos cuentan hacia atrás desde el final de la matriz (-1 es el último elemento). Devuelve cero si el índice (o índice de inicio) está fuera de rango.


También encontré la explicación de Gary Wright muy útil. 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:

-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

El error común (y comprensible) es también asumir que la semántica del índice de un solo argumento es la misma que la semántica del primer argumento en el escenario de dos argumentos (o rango). No son lo mismo en la práctica y la documentación no refleja esto. Aunque el error está definitivamente 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 nil porque no hay ningún carácter en el índice dado.

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

dos argumentos enteros: los argumentos identifican una parte 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, incluidos al principio o al final de la cadena. En este caso, el primer argumento no identifica una posición del carácter, sino que identifica el espacio entre los caracteres como se muestra en el diagrama anterior. El segundo argumento es la longitud, que puede ser 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"

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 del carácter" como con la indexación única o la "posición del borde" como con los dos argumentos enteros. La diferencia se determina según se use el rango de punto doble o el rango de punto triple:

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.

Si repasa estos ejemplos e insiste y utiliza la semántica de índice único para los ejemplos de indexación de doble o rango, simplemente se confundirá. Tienes que usar la numeración alternativa que muestro en el diagrama de ascii para modelar el comportamiento real.


Una explicación proporcionada por Jim Weirich

Una forma de pensarlo es que la posición de índice 4 se encuentra en el borde de la matriz. Al solicitar un sector, devuelve la mayor parte de la matriz que queda. Entonces, considere la matriz [2,10], la matriz [3,10] y la matriz [4,10] ... cada una devuelve los bits restantes del final de la matriz: 2 elementos, 1 elemento y 0 elementos respectivamente. Sin embargo, la posición 5 está claramente fuera de la matriz y no en el borde, por lo que la matriz [5,10] devuelve nula.


esto tiene que ver con el hecho de que slice rebaja una matriz, documentación de origen relevante de 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

lo que me sugiere que si da el comienzo que está fuera de los límites, devolverá nulo, por lo tanto, en su ejemplo, la array[4,0] solicita el cuarto elemento que existe, pero solicita devolver una matriz de cero elementos. Mientras que la array[5,0] solicita 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 de arg es dos:

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

Si observa la clase array.c donde se define el método rb_ary_subseq , verá que devuelve nulo si la longitud está fuera de los límites, no el índice:

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

En este caso, esto es lo que sucede cuando se pasan 4, comprueba que hay 4 elementos y, por lo tanto, no desencadena 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 que se evalúe el cero arg. código here en la línea 944.

Creo que esto es un error, o al menos impredecible y no el "Principio de la menor sorpresa". Cuando llegue unos minutos, al menos enviaré un parche de prueba que falla al ruby ​​core.