2010-09-12 15 views
9

I miei amici e io stiamo lavorando ad alcuni esercizi di base su Ruby per avere un'idea del linguaggio, e ci imbattiamo in un comportamento interessante che non siamo ancora in grado di capire. Fondamentalmente, stiamo creando un tipo di dati tree in cui esiste una sola classe, node, che contiene esattamente un valore e un array pari a zero o più nodes. Stiamo utilizzando il test runner autospec di rspec. A un certo punto abbiamo iniziato a scrivere test per non consentire la ricorsione infinita (una struttura ad albero circolare).Perché questa ricorsione NON è infinita?

Ecco la nostra prova:

it "breaks on a circular reference, which we will fix later" do 
    tree1 = Node.new 1 
    tree2 = Node.new 1 
    tree2.add_child tree1 
    tree1.add_child tree2 
    (tree1 == tree2).should be_false 
end 

Ecco la classe Node:

class Node 
    attr_accessor :value 
    attr_reader :nodes 

    def initialize initial_value = nil 
    @value = initial_value 
    @nodes = [] 
    end 

    def add_child child 
    @nodes.push child 
    @nodes.sort! { |node1, node2| node1.value <=> node2.value } 
    end 

    def == node 
    return (@value == node.value) && (@nodes == node.nodes) 
    end 
end 

Ci aspettiamo che l'ultima riga del test al risultato in una ricorsione infinita fino agli overflow dello stack, perché dovrebbe continuamente confrontare i nodi figlio tra loro e non trovare mai un nodo foglia. (Abbiamo l'impressione che l'operatore == su un array itererà sull'array e chiamerà == su ogni bambino, in base a the array page of RubyDoc.) Ma se lanciamo un puts nel metodo == per vedere quanto spesso viene chiamato, scopriamo che è chiamato esattamente tre volte e poi passa il test.

Cosa ci manca?

Modifica: Si noti che se si sostituisce be_false nel test con be_true, il test ha esito negativo. Quindi sicuramente pensa che gli array non siano uguali, non si ripercuote su di essi (a parte le tre chiamate distinte a ==).

+0

@beavis: "==" dovrebbe essere ricorsivo in questa implementazione, perché sta cercando di confrontare i figli di due nodi che si riferiscono l'un l'altro come bambini confrontando i loro figli e così via. È un "albero" perché è un nodo con nodi figlio, che a sua volta può avere nodi figlio, ecc. Vedi http://en.wikipedia.org/wiki/Tree_(computer_science) – David

+0

È un albero piuttosto disordinato. Un nodo non dovrebbe sapere come organizzarsi all'interno della struttura dati in cui vive. – user370731

+0

@beavis: Ti stai riferendo al tipo? Questo è qualcosa che abbiamo ritenuto utile aggiungere per testare l'ordinamento dell'array e probabilmente verrà rimosso con la maturazione del codice. È un prototipo iniziale, non destinato a essere usato per niente. Come ho detto, a questo punto ci stiamo solo facendo un'idea della lingua. – David

risposta

7

Se si fa clic sul nome del metodo della RubyDoc si è collegato al, si vedrà la sorgente (in C) del metodo Array#==:

{ 
    // [...] 
    if (RARRAY(ary1)->len != RARRAY(ary2)->len) return Qfalse; 
    if (rb_inspecting_p(ary1)) return Qfalse; 
    return rb_protect_inspect(recursive_equal, ary1, ary2); 
} 

Questa implementazione (in particolare il "recursive_equal") suggerisce che Array#== implementa già la protezione di ricorsione infinita che stai cercando.

+0

Ah, così fa. Dal punto di vista tecnico, la protezione che stiamo cercando è quella di impedirgli persino di essere ammessa sul nostro tipo di dati, ma è qualcosa per il metodo add_child. Immagino non ci sia mai passato per la testa che sarebbe tornato silenziosamente e non avrebbe generato alcun tipo di errore. Fa tutto parte dell'abituarsi alla lingua, immagino. Per curiosità, conosci la ragione del ritorno silenzioso? (Certo, non sono esperto in C, quindi sto solo vagamente seguendo questa implementazione.) – David

+1

Anche My C non è grande, ma sembra che la riga precedente 'if (rb_inspecting_p (ary1)) restituisca Qfalse; 'è ciò che in realtà attiva il falso ritorno" silenzioso ". Fondamentalmente sembra che restituisca false se incontriamo 'ary1' in qualsiasi punto * dentro *' ary1'. – Gareth

+0

@Gareth: ha senso. Non ho nemmeno notato che quelle implementazioni erano collegate su quel sito.Questo da solo ci aiuterà nei nostri sforzi (oltre a darci un po 'di più sulla lettura di C). Sapere che il controllo è lì implica che è facile da controllare, il che sarà utile nel metodo add_child. – David

Problemi correlati