Skip to content

Commit

Permalink
Merge pull request #3 from Dirkster99/Depthfirst.Traverse.PostOrder
Browse files Browse the repository at this point in the history
Depthfirst.traverse.post order
  • Loading branch information
Dirkster99 authored Jul 5, 2019
2 parents c22cb5c + c9c930b commit d066a62
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 151 deletions.
258 changes: 148 additions & 110 deletions source/Shared/Depthfirst/TraversePostOrder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
{
using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Developed out of the combined posts
Expand Down Expand Up @@ -40,131 +39,183 @@ T root
return PostOrderIterator(root, children);
}

/// <summary>
/// Implements a state keeping class that can be used in a traversal algorithm
/// to decide whether the tree should be traversed:
/// - down (towards children of a node)
/// - the node itself should be visited or whether the
/// - parents of the node should be visited.
/// </summary>
/// <typeparam name="T"></typeparam>
class VisitElement<T> : IDisposable
{
#region fields
private T _Element;
private IEnumerator<T> _EnumerateChildren;
private bool _disposed;
#endregion fields

#region ctors
/// <summary>
/// Class constructor
/// </summary>
/// <param name="element"></param>
/// <param name="enumerateChildren"></param>
public VisitElement(T element, IEnumerator<T> enumerateChildren)
: this()
{
_Element = element;
_EnumerateChildren = enumerateChildren;
}

/// <summary>
/// Hidden class constructor
/// </summary>
protected VisitElement()
{
}
#endregion ctors

#region properties
/// <summary>
/// Gets whether the children of this element have already been visited or not.
/// </summary>
public bool ChildrenVisited { get; protected set; }

/// <summary>
/// Gets whether this element has already been visited or not.
/// </summary>
public bool ThisElementVisited { get; protected set; }

/// <summary>
/// Gets the Element for this node or null if this is the root.
/// </summary>
public T Element { get { return _Element; } }
#endregion properties

#region methods
/// <summary>
/// Gets the next available child or null (if all children have been enumerated).
/// </summary>
/// <returns></returns>
public T GetNextChild()
{
if (_EnumerateChildren != null)
{
if (_EnumerateChildren.MoveNext())
return _EnumerateChildren.Current;
}

ChildrenVisited = true;
return default(T);
}

/// <summary>
/// Marks this element as having been visited.
/// </summary>
public void SetThisElementVisited()
{
ThisElementVisited = true;
}

#region Disposable Interfaces
/// <summary>
/// Standard dispose method of the <seealso cref="IDisposable" /> interface.
/// </summary>
public void Dispose()
{
Dispose(true);
}

/// <summary>
/// Source: http://www.codeproject.com/Articles/15360/Implementing-IDisposable-and-the-Dispose-Pattern-P
/// </summary>
/// <param name="disposing"></param>
protected void Dispose(bool disposing)
{
if (_disposed == false)
{
if (disposing == true)
{
// Dispose of the curently displayed content if it is disposable
if (_EnumerateChildren != null)
{
_EnumerateChildren.Dispose();
_EnumerateChildren = null;
}
}

// There are no unmanaged resources to release, but
// if we add them, they need to be released here.
}

_disposed = true;

//// If it is available, make the call to the
//// base class's Dispose(Boolean) method
////base.Dispose(disposing);
}
#endregion Disposable Interfaces
#endregion methods
}

private static IEnumerable<T> PostOrderIterator<T>(
T root
, Func<T, IEnumerable<T>> children)
{
var toVisit = new Stack<IEnumerator<T>>();
var visitedAncestors = new Stack<T>();
var toVisit = new Stack<VisitElement<T>>();

try
{
var it = new List<T> { root }.GetEnumerator();
toVisit.Push(it);
var element = new VisitElement<T>(default(T), it);
toVisit.Push(element);

while (toVisit.Count > 0)
{
T current = default(T);
var node = toVisit.Peek();
if (node.MoveNext())
{
current = node.Current;
}
else
{
// otherwise, cleanup the empty enumerator and...
node.Dispose();

// ...search up the stack for an enumerator with elements left
while (true)
if (node.ChildrenVisited == false)
{
var child = node.GetNextChild();
if (child == null)
{
if (toVisit.Count == 0)
// Is this a non-root element?
if (node.Element != null)
{
// we didn't find one, so we're all done
yield break;
}

// consider the next enumerator on the stack
var topEnumerator = toVisit.Peek();
if (topEnumerator.MoveNext())
{
// if it has an element, use it
current = topEnumerator.Current;
//break;
node.SetThisElementVisited();
yield return node.Element;
}
else
{
// otherwise discard it
toVisit.Pop().Dispose();

if (visitedAncestors.Count() > 0)
{
current = visitedAncestors.Pop();
yield return current;
}
else
yield break; // Done Done :-)
}
}
}
yield break;

if (children(current).FirstOrDefault() != null)
{
if (current.Equals(PeekOrDefault1(visitedAncestors)) == false)
continue;
}
else
{
visitedAncestors.Push(current);
PushNonReverse(toVisit, children(current));
// Put this child on the stack and continue to dive down
element = new VisitElement<T>(child, children(child).GetEnumerator());
toVisit.Push(element);
continue;
}

visitedAncestors.Pop();
}

//System.Console.WriteLine(node.GetStackPath()); // Process the node
yield return current;

//toVisit.Pop();
node = toVisit.Peek();
if (node.MoveNext())
{
current = node.Current;
yield return current;
}
else
{
node = toVisit.Pop();
// otherwise, cleanup the empty enumerator and...
node.Dispose();

// ...search up the stack for an enumerator with elements left
while (true)
if (node.ThisElementVisited == false)
{
if (toVisit.Count == 0)
// Is this a non-root element?
if (node.Element != null)
{
// we didn't find one, so we're all done
yield break;
}

// consider the next enumerator on the stack
var topEnumerator = toVisit.Peek();

current = topEnumerator.Current;
visitedAncestors.Pop();
yield return current;

if (topEnumerator.MoveNext())
{
// if it has an element, use it
current = topEnumerator.Current;

if (children(current).FirstOrDefault() != null)
{
if (current.Equals(PeekOrDefault1(visitedAncestors)) == false)
{
visitedAncestors.Push(current);
PushNonReverse(toVisit, children(current));
break;
}
}

yield return current;
node.SetThisElementVisited();
yield return node.Element;
}
else
{
// otherwise discard it
toVisit.Pop().Dispose();
}
yield break;

continue;
}
else // This node and its children have been visited
toVisit.Pop();
}
}
}
Expand All @@ -189,19 +240,6 @@ private static T PeekOrDefault1<T>(Stack<T> s)
return s.Count == 0 ? default(T) : s.Peek();
}

/// <summary>
/// Push all children of a given node in reverse order into the
/// <seealso cref="Stack{T}"/> <paramref name="s"/>.
///
/// Use this to traverse the tree from left to right.
/// </summary>
/// <param name="s"></param>
/// <param name="list"></param>
private static void PushReverse<T>(Stack<IEnumerator<T>> s, IEnumerable<T> list)
{
s.Push(list.ToArray().Reverse().GetEnumerator());
}

/// <summary>
/// Push all children of a given node in givrn order into the
/// <seealso cref="Stack{T}"/> <paramref name="s"/>.
Expand Down
2 changes: 1 addition & 1 deletion source/TreeLib/Readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A store (serialize) and restore (deserialize) algorithm usually requires a
LevelOrder or PreOrder traversal from the root node(s) down to the bottom,
in which, each node is visited exactly once.

Other algorithms, that require use to compute a value from the leaf up to the root
Other algorithms, that require us to compute a value from the leaf up to the root
may require a PostOrder traversal. This solution contains a short overview on this
topic (see below) and Generic algorithms that can be used on any tree (be it binary
or n-ary - each node can have n children with any n > 0).
Expand Down
13 changes: 7 additions & 6 deletions source/TreeLib/TreeLib.csproj
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>This library contains Generic algorithm implementations for tree traversals, such as, LevelOrder, PreOrder, and PostOrder.</Description>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>

<TargetFramework>netstandard1.6</TargetFramework>
<PackageId>Dirkster.TreeLib</PackageId>
<PackageVersion>1.0.0</PackageVersion>
<Authors>Dirk Bahle</Authors>
<Description>This library contains Generic algorithm implementations for tree traversals, such as, LevelOrder, PreOrder, and PostOrder.</Description>
<Description>https://github.com/Dirkster99/TreeLib
This library contains Generic algorithm implementations for tree traversals, such as, LevelOrder, PreOrder, and PostOrder. Each algorithm is available with a visitor pattern that returns IEnumerable&lt;T&gt;, which means these implementations should scale very well for traversing large trees (with a lot of child nodes and/or depth).</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>First release</PackageReleaseNotes>
<Copyright>Copyright 2017 (c) Dirk Bahle. All rights reserved.</Copyright>
<PackageReleaseNotes>Second release with Fixed PostOrder Algorithm that returns IEnumerable&lt;T&gt;.</PackageReleaseNotes>
<Copyright>Copyright 2017-2019 (c) Dirk Bahle. All rights reserved.</Copyright>
<PackageTags>generic tree traversal method LevelOrder PreOrder PostOrder</PackageTags>
<AssemblyVersion>1.1.0.0</AssemblyVersion>
<FileVersion>1.1.0.0</FileVersion>
<AssemblyVersion>1.2.0.0</AssemblyVersion>
<FileVersion>1.2.0.0</FileVersion>
<Version>1.2.0</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
6 changes: 3 additions & 3 deletions source/TreeLibDemo/App.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
</startup>
</configuration>
</configuration>
3 changes: 2 additions & 1 deletion source/TreeLibDemo/TreeLibDemo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
<OutputType>Exe</OutputType>
<RootNamespace>TTraversalDemo</RootNamespace>
<AssemblyName>TTraversalDemo</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
Expand Down
3 changes: 2 additions & 1 deletion source/TreeLibDemoLib/TreeLibDemoLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TreeLibDemoLib</RootNamespace>
<AssemblyName>TreeLibDemoLib</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down
Loading

0 comments on commit d066a62

Please sign in to comment.