237 votes

Array slicing in Ruby : explication d'un comportement illogique (tiré de Rubykoans.com)

Je faisais les exercices dans Koans de Rubis et j'ai été frappé par la bizarrerie suivante de Ruby que je trouvais vraiment 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...

Alors pourquoi array[5,0] non égal à array[4,0] ? Y a-t-il une raison pour laquelle le découpage d'un tableau se comporte aussi bizarrement lorsque l'on commence à la (longueur+1) th position ?

3 votes

0 votes

On dirait que le premier chiffre est l'index de départ, le second le nombre d'éléments à découper.

190voto

Amadan Points 41944

Le découpage et l'indexation sont deux opérations différentes, et c'est en déduisant le comportement de l'une à partir de l'autre que se situe votre problème.

Le premier argument de slice n'identifie pas l'élément mais les endroits entre les éléments, définissant les spans (et non les éléments eux-mêmes) :

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

4 est toujours dans le tableau, à peine ; si vous demandez 0 élément, vous obtenez l'extrémité vide du tableau. Mais il n'y a pas d'indice 5, donc vous ne pouvez pas couper à partir de là.

Lorsque vous faites un index (comme array[4] ), vous pointez sur les éléments eux-mêmes, donc les indices ne vont que de 0 à 3.

9 votes

Une bonne supposition, à moins qu'elle ne soit confirmée par la source. Sans vouloir être désagréable, je serais intéressé par un lien, s'il y en a un, pour expliquer le "pourquoi", comme le demandent le PO et d'autres commentateurs. Votre diagramme est logique, sauf que Array[4] est nul. Array[3] est :jelly. Je m'attendrais à ce que Array[4,N] soit nul mais c'est [] comme le dit l'OP. Si c'est un lieu, c'est un lieu plutôt inutile car Array[4, -1] est nul. Vous ne pouvez donc rien faire avec Array[4].

5 votes

@squarism Je viens d'avoir la confirmation de Charles Oliver Nutter (@headius sur Twitter) que c'est l'explication correcte. C'est un grand développeur de JRuby, donc je considère que sa parole fait autorité.

19 votes

Ce qui suit est la justification de ce comportement : blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/380637

28voto

Jed Schneider Points 6253

Cela a à voir avec le fait que slice renvoie un tableau, documentation source pertinente 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

ce qui me suggère que si vous donnez un début qui est hors limites, il retournera nil, donc dans votre exemple array[4,0] demande le 4ème élément qui existe, mais demande de retourner un tableau de zéro éléments. Alors que array[5,0] demande un index hors limites et renvoie donc nil. Cela a peut-être plus de sens si l'on se rappelle que la méthode slice renvoie un fichier nouveau sans modifier la structure de données originale.

EDITAR:

Après avoir examiné les commentaires, j'ai décidé de modifier cette réponse. La tranche appelle ce qui suit extrait de code lorsque la valeur de l'arg est de deux :

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 vous regardez dans le array.c où le rb_ary_subseq est définie, vous voyez qu'elle renvoie nil si la longueur est hors limites, et non l'index :

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

Dans ce cas, c'est ce qui se passe lorsque 4 est passé, il vérifie qu'il y a 4 éléments et ne déclenche donc pas le retour nil. Il retourne ensuite un tableau vide si le second argument est égal à zéro. Alors que si 5 est passé, il n'y a pas 5 éléments dans le tableau, donc il retourne nil avant que l'argument zéro soit évalué. code aquí à la ligne 944.

Je pense qu'il s'agit d'un bogue, ou du moins d'un phénomène imprévisible et non du "principe de la moindre surprise". Quand j'aurai quelques minutes, je soumettrai au moins un patch de test défaillant à ruby core.

2 votes

Mais... l'élément indiqué par le 4 dans le tableau [4,0] n'existe pas non plus... - car il s'agit en fait du 5ème élément (comptage basé sur 0, voir les exemples). Il est donc lui aussi hors limites.

1 votes

Vous avez raison. Je suis retourné et j'ai regardé la source, et il semble que le premier argument est traité dans le code c comme la longueur, et non l'index. Je vais modifier ma réponse, pour refléter cela. Je pense que cela pourrait être soumis comme un bug.

23voto

Matchu Points 37755

Notez au moins que le comportement est cohérent. À partir de 5, tout se comporte de la même manière ; la bizarrerie ne se produit qu'au niveau de [4,N] .

Peut-être que ce modèle aide, ou peut-être que je suis juste fatiguée et que ça n'aide pas du tout.

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

Sur [4,0] on arrive à la fin du tableau. En fait, je trouverais plutôt étrange, du point de vue de la beauté des motifs, que le dernier tableau retourne nil . A cause d'un contexte comme celui-ci, 4 est une option acceptable pour le premier paramètre afin que le tableau vide puisse être retourné. Cependant, dès que nous atteignons 5 et plus, la méthode s'éteint immédiatement car elle est totalement et complètement hors limites.

12voto

Frank Szczerba Points 2767

Cela est logique si l'on considère qu'une tranche de tableau peut être une valeur lval valide, et pas seulement une valeur 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]

Cela ne serait pas possible si array[4,0] a retourné nil au lieu de [] . Cependant, array[5,0] renvoie à nil parce qu'il est hors limites (insérer après le 4e élément d'un tableau à 4 éléments est significatif, mais insérer après le 5e élément d'un tableau à 4 éléments ne l'est pas).

Lire la syntaxe de la tranche array[x,y] comme "commençant après x éléments dans array sélectionnez jusqu'à y éléments". Cela n'a de sens que si array a au moins x éléments.

10voto

DigitalRoss Points 80400

Ce site fait être sensé

Vous devez pouvoir assigner à ces tranches, elles sont donc définies de telle sorte que le début et la fin de la chaîne ont des expressions de longueur zéro qui fonctionnent.

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

1 votes

Vous pouvez également assigner à la plage la tranche qui retourne comme nil, il serait donc utile de développer cette explication. array[5,0]=:foo # array is now [:peanut, :butter, :and, :jelly, nil, :foo]

0 votes

À quoi sert le deuxième chiffre lors de l'attribution ? il semble être ignoré. [26] pry(main)> array[4,5] = [:love, :hope, :peace] => [:peanut, :butter, :and, :jelly, :love, :hope, :peace]

0 votes

@drewverlee it n'est pas ignoré : array = [:a, :b, :c, :d, :e]; array[1,2] = :x, :x; array => [:a, :x, :x, :d, :e]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X