C#: unikaj nieskończonej rekurencji podczas przechodzenia wykresu obiektów

Mam Wykres obiektu, w którym każdy obiekt potomny zawiera właściwość, która odwołuje się do jego rodzica. Czy istnieją jakieś dobre strategie ignorowania referencji nadrzędnych w celu uniknięcia nieskończonej rekurencji? Rozważałem dodanie specjalnego [rodzica] atrybutu do tych właściwości lub użycie specjalnej konwencji nazewnictwa, ale być może istnieje lepszy sposób.

Author: nw., 2010-02-05

6 answers

Jeśli pętle mogą być uogólnione (możesz mieć dowolną liczbę elementów tworzących pętlę), możesz śledzić obiekty, które już widziałeś w HashSet i zatrzymać, jeśli obiekt jest już w zestawie podczas jego odwiedzania. Lub dodaj flagę do obiektów, które ustawiasz podczas odwiedzania (ale potem musisz wrócić i wyłączyć wszystkie flagi, gdy skończysz, a Wykres może być przemierzany tylko przez pojedynczy wątek na raz).

Alternatywnie, jeśli pętle zostaną zwrócone tylko do rodzica, może zachować odniesienie do rodzica, a nie pętlę na właściwościach, które odnoszą się do niego.

Dla uproszczenia, jeśli wiesz, że Referencja rodzica będzie miała określoną nazwę, po prostu nie możesz pętli na tej właściwości:)

 30
Author: thecoop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-05 17:57:33

Co za zbieg okoliczności; to jest temat mojego bloga w najbliższy poniedziałek. Zobacz go po więcej szczegółów. Do tego czasu, oto kod, który da ci pomysł, jak to zrobić:

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;
            }
        }
    } 
}

Metoda przyjmuje dwie rzeczy: element i relację, która tworzy zbiór wszystkiego, co przylega do elementu. Tworzy ona Depth-first traversal of the transitive and reflexive closure of the adjacency relation on the items . Niech liczba elementów na wykresie będzie n, A maksymalna głębokość wynosi 1

Zauważ, że szczytowe wykorzystanie pamięci tego algorytmu wynosi oczywiście O(n + d) = O (n).

Więc dla przykład:

foreach(Node node in Traversal(myGraph.Root, n => n.Children))
  Console.WriteLine(node.Name);

Ma sens?

 32
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-14 15:19:39

Jeśli robisz przemierzanie wykresu, możesz mieć flagę" odwiedzony " na każdym węźle. Zapewnia to, że nie odwiedzisz ponownie węzła i prawdopodobnie utkniesz w nieskończonej pętli. Uważam, że jest to standardowy sposób wykonywania grafu.

 2
Author: Vivin Paliath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-05 17:08:06

Nie jestem do końca pewien, co próbujesz zrobić tutaj, ale możesz po prostu utrzymać hashtable ze wszystkimi wcześniej odwiedzonymi węzłami, gdy robisz swoje szerokość pierwsze wyszukiwanie głębi pierwsze wyszukiwanie.

 2
Author: rui,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-05 17:09:38

Jest to częsty problem, ale najlepsze podejście zależy od scenariusza. Dodatkowym problemem jest to, że w wielu przypadkach nie jest to problem dwukrotnego odwiedzenia tego samego obiektu - co nie implikuje rekurencji-na przykład rozważ drzewo:

A
=> B
   => C
=> D
   => C

Może to być poprawne (pomyśl XmlSerializer, które po prostu zapisywałyby instancję C dwa razy), więc często konieczne jest wypychanie / pop obiektów na stosie, aby sprawdzić prawdziwą rekurencję. Ostatnim razem, kiedy wprowadziłem "gościa", zachowałem " głębię" licznik, i tylko włączone sprawdzanie stosu poza określonym progiem-oznacza to, że Większość drzew po prostu kończy się wykonywaniem niektórych ++/--, ale nic droższego. Możesz zobaczyć podejście, które zastosowałem tutaj .

 2
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-05 18:22:51

Opublikowałem post wyjaśniający szczegółowo za pomocą przykładów kodu, jak wykonać object traversal przez rekurencyjne odbicie, a także wykrywać i unikać rekurencyjnych odniesień, aby zapobiec wyjątkowi stosu nad przepływem: https://doguarslan.wordpress.com/2016/10/03/object-graph-traversal-by-recursive-reflection/

W tym przykładzie zrobiłem najpierw głębię, używając rekurencyjnego odbicia i utrzymałem HashSet odwiedzanych węzłów dla typów referencyjnych. Należy uważać, aby zainicjować Twój HashSet z niestandardowym porównaniem Równości, który wykorzystuje odniesienie do obiektu do obliczenia hash, w zasadzie metoda GetHashCode () zaimplementowana przez samą klasę obiektu bazowego, a nie przeciążone wersje GetHashCode (), ponieważ jeśli typy właściwości, które przemierzasz, przeciążają metodę GetHashCode, możesz wykryć fałszywe kolizje hash i pomyśleć, że wykryłeś rekurencyjne odniesienie, które w rzeczywistości może być, że przeciążona wersja GetHashCode produkująca tę samą wartość hash przez niektóre heurystyki i mylące HashSet, wszystko, co musisz wykryć, to sprawdzić, czy w dowolnym miejscu w drzewie obiektów znajduje się jakieś dziecko-rodzic wskazujące na to samo miejsce w pamięci.

 0
Author: Dogu Arslan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-04 08:43:01