2010-03-24 10 views
2

J'essaye de sous-classer Array dans ruby ​​pour lui faire randomiser ses éléments lorsqu'il est aplati! est appelé. En regardant le code source de Array # flatten (http://ruby-doc.org/core/classes/Array.src/M002218.html), on dirait qu'il devrait appeler récursivement aplatir! sur n'importe quel tableau contenu dans un tableau. Donc, j'ai essayé de faire quelque chose comme ceci:Ruby - Tableau de sous-classes pour le rendre aléatoire quand il est aplati

class RandArray < Array 
    def randomize! 
     self.sort!{rand(3)-1} 
    end 
    def flatten! 
     randomize! 
     super 
    end 
end 

Cependant, lorsqu'un tableau normal contient mon RandArray et aplatissez est appelé sur le tableau normal, aplatir! n'est jamais appelé dans mon tableau. Je pense que Ruby appelle simplement une autre méthode pour aplatir les tableaux récursivement, mais je ne peux pas comprendre ce que c'est. Des conseils?

Répondre

3

Je ne suis pas un expert absolu à ce sujet mais Ruby's Array est écrit en tant que code C. voici le code pour aplatir! :

static VALUE 
rb_ary_flatten_bang(ary) 
    VALUE ary; 
{ 
    long i = 0; 
    int mod = 0; 
    VALUE memo = Qnil; 

    while (i<RARRAY(ary)->len) { 
     VALUE ary2 = RARRAY(ary)->ptr[i]; 
     VALUE tmp; 

     tmp = rb_check_array_type(ary2); 
     if (!NIL_P(tmp)) { 
      if (NIL_P(memo)) { 
       memo = rb_ary_new(); 
      } 
      i += flatten(ary, i, tmp, memo); 
      mod = 1; 
     } 
     i++; 
    } 
    if (mod == 0) return Qnil; 
    return ary; 
} 

Comme vous pouvez le voir sur cette ligne,

i += flatten(ary, i, tmp, memo); 

et voici la mise en œuvre de cette fonction aplatissez C:

static long 
flatten(ary, idx, ary2, memo) 
    VALUE ary; 
    long idx; 
    VALUE ary2, memo; 
{ 
    VALUE id; 
    long i = idx; 
    long n, lim = idx + RARRAY(ary2)->len; 

    id = rb_obj_id(ary2); 
    if (rb_ary_includes(memo, id)) { 
    rb_raise(rb_eArgError, "tried to flatten recursive array"); 
    } 
    rb_ary_push(memo, id); 
    rb_ary_splice(ary, idx, 1, ary2); 
    while (i < lim) { 
    VALUE tmp; 

    tmp = rb_check_array_type(rb_ary_elt(ary, i)); 
    if (!NIL_P(tmp)) { 
     n = flatten(ary, i, tmp, memo); 
     i += n; lim += n; 
    } 
    i++; 
    } 
    rb_ary_pop(memo); 

    return lim - idx - 1; /* returns number of increased items */ 
} 

Le aplatissent! code appelle directement la fonction C flatten pour tout élément du tableau qui valide rb_check_array_type il ne retourne pas au code ruby. Au lieu de cela, il accède à la structure C sous-jacente en contournant directement votre implémentation surchargée.

Je ne sais pas comment contourner cela, je pense que l'one-way pourrait être de rouvrir le tableau et réécrire l'aplatir et aplatir! fonctionne comme du rubis pur. Vous obtiendriez un coup de performance, mais alors vous seriez en mesure de le surcharger comme bon vous semble. Et vous pouvez toujours utiliser un alias pour avoir un "flatten_native" et un "flatten_native!" Fonctionne sur votre tableau modifié, pour récupérer les perfs sur certains cas.

1

Jean est correct, aplatir appelle une fonction C dans les coulisses. Vous pouvez patcher la classe Array et remplacer la valeur par défaut aplatie! méthode, tout en conservant l'accès à la méthode d'origine.

class Array 
    alias_method :old_flatten!, :flatten! 
    def flatten! 
    self.old_flatten! 
    self.sort!{rand(3)-1} 
    end 
end 

Ou vous pourriez simplement ajouter un flatten_with_randomize! à la classe Array et utilisez-la à la place et gardez l'original à plat! méthode intacte.

+0

Cela rendrait aléatoire tous les tableaux, pas seulement les RandArrays dans un tableau normal, bien que ... – Jean

+1

C'est vrai. Cela pourrait être un problème, c'est pourquoi j'irais avec un flatten_with_randomize! méthode. Pas besoin de ré-implémenter aplatir! en rubis, et de prendre n'importe quel coup de performance. – Teoulas