Tengo un gráfico de objetos en el que cada objeto secundario contiene una propiedad que hace referencia a su elemento primario. ¿Hay alguna buena estrategia para ignorar las referencias principales a fin de evitar la recursión infinita? He considerado agregar un atributo especial [Parent] a estas propiedades o usar una convención de nomenclatura especial, pero tal vez haya una mejor manera.C#: Evitar recurrencia infinita cuando se atraviesa el gráfico de objetos
Respuesta
Si los bucles se pueden generalizar (puede tener cualquier número de elementos que componen el bucle), puede realizar un seguimiento de los objetos que ya ha visto en un HashSet
y detener si el objeto ya está en el conjunto cuando visitarla. O agregue una bandera a los objetos que establece cuando la visita (pero luego tiene que volver atrás & desarmar todas las banderas cuando haya terminado, y el gráfico solo puede atravesarse con un solo hilo a la vez).
Alternativamente, si los bucles solo volverán al elemento principal, puede mantener una referencia al elemento principal y no repetir las propiedades que hacen referencia a él.
Para simplificar, si conoce la referencia de los padres tendrá un cierto nombre, usted podría no han de enlazarse en esa propiedad :)
Mi solución final fue pasar una referencia "principal" con cada llamada recursiva, luego omitir cualquier propiedad que haga referencia al padre. ¡Funciona genial! –
@nw: ¿y si tienes el nodo A con el niño B, el nodo B con el niño C y el nodo C con el niño A? Pasar solo la referencia principal no siempre funciona. –
@Eric Lippert: Buen punto. En mi caso, el gráfico es más o menos jerárquico, por lo que la solución más simple será suficiente. –
Si estás haciendo un recorrido gráfico, puede tener un "visitada" bandera en cada nodo. Esto garantiza que no vuelva a visitar un nodo y posiblemente se quede atascado en un bucle infinito. Creo que esta es la forma estándar de realizar un cruce de gráficos.
A menos que el gráfico sea compartido por muchos hilos. En ese caso, debe mantener la información en algún lugar del hilo actual. –
De hecho, es peor que el escenario de @ Victor. Supongamos que desea hacer una consulta sobre el producto cartesiano del gráfico * consigo mismo *. La forma ingenua de hacerlo es atravesar el gráfico, y luego, para cada elemento en el recorrido, iniciar un nuevo recorrido. Ahora necesita poder recorrer el mismo gráfico dos veces al mismo tiempo * en el mismo hilo *. Establecer una bandera es una forma estándar de hacer esto, sí, pero también es una mala idea. Atravesar un gráfico no debe mutarlo. –
Esos son casos especiales. Acabo de proporcionar una solución general que debería funcionar para el caso más común: un hilo atravesando un gráfico * una vez *. Situaciones más complicadas requerirán otras soluciones. –
No estoy exactamente seguro de lo que estás tratando de hacer aquí, pero podrías mantener una tabla hash con todos los nodos previamente visitados cuando estés haciendo tu primera búsqueda de profundidad de la primera búsqueda.
Qué casualidad; este es el tema de my blog este próximo lunes. Véalo para más detalles. Hasta entonces, aquí hay un código para darle una idea de cómo hacer esto:
static IEnumerable<T> Traversal<T>(
T item,
Func<T, IEnumerable<T>> children)
{
var seen = new HashSet<T>();
var stack = new Stack<T>();
seen.Add(item);
stack.Push(item);
yield return item;
while(stack.Count > 0)
{
T current = stack.Pop();
foreach(T newItem in children(current))
{
if (!seen.Contains(newItem))
{
seen.Add(newItem);
stack.Push(newItem);
yield return newItem;
}
}
}
}
El método tiene dos cosas: un elemento y una relación que produce el conjunto de todo lo que está al lado del elemento. Produce un recorrido en profundidad de del cierre transitivo y reflexivo de la relación de adyacencia en el artículo. Deje que el número de elementos en el gráfico sea n, y la profundidad máxima sea 1 < = d < = n, suponiendo que el factor de bifurcación no está limitado. Este algoritmo usa una pila explícita en lugar de recursión porque (1) la recursividad convierte en este caso lo que debería ser un algoritmo O (n) en O (nd), que es algo entre O (n) y O (n^2). y (2) recursión excesiva puede explotar la pila si la d es más que algunos cientos de nodos.
Tenga en cuenta que el uso máximo de la memoria de este algoritmo es, por supuesto, O (n + d) = O (n).
Así, por ejemplo:
foreach(Node node in Traversal(myGraph.Root, n => n.Children))
Console.WriteLine(node.Name);
sentido?
Fuera de interés, ¿cuál es el límite general para la cantidad de cuadros de apilamiento que puede tener antes de lanzar una StackOverflowException? – thecoop
@thecoop: Obtienes por defecto un millón de bytes de pila por hilo. ¿Cuántos bytes ocupa una activación determinada depende de detalles como "¿qué tan grande es una referencia?" (64 bits en máquinas de 64 bits, 32 bits en máquinas de 32 bits ...) por lo que es difícil dar una declaración precisa de los límites. No me sentiría cómodo con una recursividad de más de unos cientos de profundidad. –
Este es un problema común, pero el mejor enfoque depende del escenario. Un problema adicional es que en muchos casos no es un problema visitar el mismo objeto dos veces - que no implica la recursividad - por ejemplo, considere el árbol:
A
=> B
=> C
=> D
=> C
Esto puede ser válido (piensa XmlSerializer
, lo que simplemente escribiría la instancia C
dos veces), por lo que a menudo es necesario insertar objetos en una pila para verificar la verdadera recursión.La última vez que implementé un "visitante", mantuve un contador de "profundidad", y solo habilité la verificación de la pila más allá de un cierto umbral - eso significa que árboles más simplemente terminan haciendo algo de ++
/--
, pero nada más caro. Puedes ver el enfoque que tomé here.
alas. el enlace ahora está muerto. – el2iot2
también este árbol de ejemplo no es recursivo – BooNonMooN
@BooNooMooN no pretende ser; está señalando que el seguimiento de los nodos visitados ** no implica necesariamente necesariamente la recursión (cuando se vuelva a visitar) –
I publicó un post explicando en detalle con ejemplos de código de cómo hacer traversal objeto por reflexión recursiva y también detectar y evitar referencias recursivas para prevenir una pila sobre excepción flujo: https://doguarslan.wordpress.com/2016/10/03/object-graph-traversal-by-recursive-reflection/
En ese ejemplo hice una profundidad primero recorrido transversal usando reflexión recursiva y mantuve un HashSet de nodos visitados para tipos de referencia. Una cosa a tener en cuenta es inicializar su HashSet con su comparador de igualdad personalizado que utiliza la referencia de objeto para el cálculo de hash, básicamente el método GetHashCode() implementado por la clase de objeto base en sí y no cualquier versión sobrecargada de GetHashCode() porque si los tipos de propiedades atraviesa el método GetHashCode sobrecarga, puede detectar falsas colisiones hash y cree que ha detectado una referencia recursiva que en realidad podría ser que la versión sobrecargada de GetHashCode produzca el mismo valor hash a través de algunas heurísticas y confunda el HashSet, todo lo que necesita detectar es comprobar si hay algún hijo principal en cualquier parte del árbol de objetos que apunta a la misma ubicación en la memoria.
- 1. ¿Cómo evita PHP recurrencia infinita aquí?
- 2. 0x8000500C error ActiveDirectory cuando se atraviesa propiedades
- 3. ¿Cómo atraviesa jQuery el DOM?
- 4. ¿Cómo puedo evitar la recursión infinita cuando uso eventos para vincular elementos de IU con campos?
- 5. Consejos prácticos para depurar la recurrencia profunda?
- 6. Entity Framework - Separar y mantener el gráfico de objetos relacionados
- 7. C# - ¿Los objetos se destruyen inmediatamente cuando salen del alcance?
- 8. C Sockets: evitar basura cuando el socket está cerrado
- 9. ¿Cómo evitar fugas de memoria cuando se usa un vector de punteros a objetos dinámicamente asignados en C++?
- 10. Gráfico de la línea KendoUI, ¿Cómo evitar que las etiquetas se dibujen fuera del gráfico?
- 11. Programando un gráfico orientado a objetos simple en C++
- 12. ¿Detecta recursión infinita?
- 13. NHibernate Future gráfico de objetos muchas consultas
- 14. Secuencia infinita con elementos que se repiten
- 15. Evitar que UIWebView se desplace cuando aparezca el teclado
- 16. REST Diseño de API para actualizar el gráfico de objetos
- 17. Ruby: Patrones de recurrencia estándar
- 18. SAXException2: Se detecta un ciclo en el gráfico de objetos. ¿Cuál es el caso?
- 19. Convertir recursivamente el gráfico de objetos de Python al diccionario
- 20. Mejorando en recurrencia de bucle
- 21. Usando la interfaz IXmlSerializable el gráfico de objetos complejos
- 22. remove_vertex cuando el gráfico VertexList = vecS
- 23. C#: eliminación correcta de objetos C# cuando se creó a través de COM Interop desde VB6
- 24. serialización de Java, Kryo y el gráfico de objetos
- 25. Groovy: cómo establecer una propiedad dentro de setProperty() y evitar la recursión infinita?
- 26. UIButton se pone oscuro cuando se selecciona (quiero evitar esto)
- 27. jQuery función infinita ejecución
- 28. Excepción Cuando se lanza Cadena de objetos para SqlString
- 29. ¿Puedo evitar que el indicador de divulgación de una celda se vuelva blanco cuando se selecciona?
- 30. Evitar que el evento click se active cuando se activa el evento dblclick
No creo que tener una relación padre/hijo inherentemente conduce a la recursión infinita. Sería útil si mostró el código que está causando el problema. –
@klausbyskov - si tiene navegabilidad bidireccional en la relación padre-hijo (puede navegar la relación en ambas direcciones), entonces tiene recursión infinita. El padre tiene una propiedad que lo lleva al niño que tiene una propiedad que lo lleva de vuelta al padre, que tiene una propiedad que lo lleva al niño, que tiene una propiedad ... etc. etc. –
@Rob Levine: estoy seguro que estará de acuerdo en que si tener o no una relación padre/hijo dará lugar a una recursión infinita depende de la forma en que recorra el gráfico del objeto. –