2010-09-12 41 views
9

Mis amigos y yo estamos trabajando en algunos ejercicios básicos de Ruby para familiarizarnos con el idioma, y ​​hemos encontrado un comportamiento interesante que todavía no hemos podido comprender. Básicamente, estamos creando un tipo de datos tree donde solo hay una clase, node, que contiene exactamente un valor y una matriz de cero o más nodes. Estamos usando el corredor de prueba autospec de rspec. En un momento comenzamos a escribir pruebas para rechazar la recursión infinita (una estructura de árbol circular).¿Por qué esta recursión NO es infinita?

Aquí está nuestra prueba:

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 

Aquí está la clase 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 

Esperamos que la última línea de la prueba para dar lugar a un bucle infinito hasta que los desbordamientos de pila, ya que debe continuamente compare los nodos secundarios entre sí y nunca encuentre un nodo hoja. (Estamos bajo la impresión de que el operador == en una matriz iterará sobre la matriz y llamará al == en cada niño, según the array page of RubyDoc). Pero si lanzamos un puts en el método == para ver la frecuencia con que se llama, descubrimos que se llama exactamente tres veces y luego pasa la prueba.

¿Qué nos falta?

Editar: Tenga en cuenta que si reemplazamos be_false en la prueba con be_true, la prueba falla. Por lo tanto, definitivamente cree que las matrices no son iguales, simplemente no está recurriendo a ellas (aparte de las tres llamadas distintas al ==).

+0

@beavis: "==" debe ser recursivo en esta implementación, porque trata de comparar los elementos secundarios de dos nodos que se referencian entre sí cuando son niños comparando a sus hijos, y así sucesivamente. Es un "árbol" porque es un nodo con nodos secundarios, que a su vez puede tener nodos secundarios, etc. Ver http://en.wikipedia.org/wiki/Tree_(computer_science) – David

+0

Es un árbol bastante desordenado. Un nodo no debería saber cómo organizarse dentro de la estructura de datos en la que vive. – user370731

+0

@beavis: ¿Te refieres al género? Eso fue algo que quisimos agregar para probar la ordenación de arreglos y probablemente se eliminará a medida que el código madure. Es un prototipo temprano, que en realidad no se usará para nada. Como dije, solo estamos sintiendo el idioma en este momento. – David

Respuesta

7

Si hace clic en el nombre del método de la RubyDoc se enlazó a, verá la fuente (en C) del método 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); 
} 

Esta aplicación (en concreto el "recursive_equal") sugiere que Array#== ya implementa la protección infinita de recursión que busca.

+0

Ah, entonces sí. Técnicamente, la protección que buscamos es, en última instancia, evitar que se permita en nuestro tipo de datos, pero eso es algo para el método add_child. Supongo que nunca se nos ocurrió que regresaría silenciosamente y no arrojaría algún tipo de error. Es todo parte de acostumbrarse al lenguaje, supongo. Por curiosidad, ¿conoces la razón del regreso silencioso? (Es cierto que no estoy muy versado en C, así que solo estoy siguiendo vagamente esta implementación.) – David

+1

Mi C tampoco es grande, pero parece que la línea anterior 'if (rb_inspecting_p (ary1)) devuelve Qfalse; 'es lo que realmente desencadena el falso retorno" silencioso ". Básicamente parece devolver falso si nos encontramos con 'ary1' en cualquier punto * dentro *' ary1'. – Gareth

+0

@Gareth: tiene sentido. Ni siquiera noté esas implementaciones estaban vinculadas en ese sitio.Eso solo nos ayudará en nuestros esfuerzos (y también nos ayudará un poco a leer C). Saber que ese control está ahí implica para mí que es fácil de verificar, lo cual será útil en el método add_child. – David

Cuestiones relacionadas