ExtensionMethodsSample_ja - Houzkin/TreeStructures GitHub Wiki

拡張メソッド

詳細はソースコードを参照

ITreeNode

数が多いので、複雑なメソッドをピックアップします。

列挙

source code

Traverse

/// <summary>
/// 現在のノードを起点としてツリー構造を展開し列挙します。  
/// 列挙の過程でノードを追加・更新するためのカスタムロジックを指定できます。
/// </summary>
/// <typeparam name="T">ノードの型。</typeparam>
/// <param name="startNode">列挙を開始する起点のノード。</param>
/// <param name="getNodes">
/// 指定したノードに基づいて追加すべきノードを決定する関数。  
/// 現在のノードを引数に取り、それに関連するノードのコレクションを返します。
/// </param>
/// <param name="updatePendingNodes">
/// 列挙中にまだ列挙されていないノードのリストを更新するための関数。  
/// この関数は以下の引数を取ります:
/// 第1引数:現在のノード。<paramref name="getNodes"/> に最初の引数として渡された値です。
/// 第2引数:現在のノードに対して追加された新しいノードのコレクション。<paramref name="getNodes"/> の戻り値です。
/// 第3引数:まだ列挙されていない残りのコレクション。  
/// 戻り値:このコレクションの先頭要素がまだ <paramref name="getNodes"/> に渡されていない場合、その要素が次に渡されます。
/// すでに <paramref name="getNodes"/> に渡された要素であれば、その要素は列挙されます。
/// </param>
public static IEnumerable<T> Traverse<T>(this ITreeNode<T> startNode, 
    Func<T, IEnumerable<T?>> getNodes, 
    Func<T, IEnumerable<T?>,IEnumerable<T?>, IEnumerable<T?>> updatePendingNodes)
    where T : ITreeNode<T> { }

Traverseメソッドは様々な列挙方法を実現できる、汎用性に富んだメソッドです。
ライブラリ内においても頻繁に使用されていますが、引数と列挙の挙動が難解なため補足をしておきます。

PreOrderを実現するために以下のように記述しています。

public static IEnumerable<T> PreOrder<T>(this ITreeNode<T> self) where T : ITreeNode<T>{
    return self.Traverse(
        a => a.Children,
        (a, b, c) => b.Prepende(a).Concat(c));// a, b[...], c[...]
}

getNodesで指定するa => a.Childrenによって列挙に加えるノードを取得します。
次の引数updatePendingNodesでは、この戻り値が第2引数:bに割り当てられ、(a,b,c)=>b.Prepend(a).Concat(c)と、列挙の順序を指定しています。
実際の順序としては、a, a.Children[0], a.Children[1]..., C[0], C[1]... となります。
ここで先頭の要素aは既にgetNodesの引数として使用されているのでaが列挙されます。
そして、次の要素a.Children[0]に対してgetNodesの関数が適用され、a.Children[1]以降の要素がupdatePendingNodesの第3引数:cとして使用されます。

同様にPostOrderの挙動を追ってみます。

public static IEnumerable<T> PostOrder<T>(this ITreeNode<T> self) where T : ITreeNode<T>{
    return self.Traverse(
        a => a.Children,
        (a, b, c) => b.Append(a).Concat(c));// b[...], a, c[...]
}

getNodesに関してはPreOrderと同じです。
updatePendingNodesではabの順序がPreOrderと逆になります。
順序としては、a.Children[0], a.Children[1]..., a, C[0], C[1]... です。
ここで先頭のa.Children[0]はまだgetNodesの引数として使用されていません。
よって、a.Children[0]は列挙されないままgetNodesが適用され、a.Children[1]以降の要素がそのままupdatePendingNodesの第3引数:cとして使用されます。

このような振る舞いを応用して、祖先を遡る場合は

public static IEnumerable<T> Upstream<T>(this ITreeNode<T> self) where T : ITreeNode<T> {
    return self.Traverse(
        a => new T?[1] { a.Parent }, 
        (a, b, c) => new T?[1] { a }.Concat(b).Concat(c));
}

Leafのみを列挙する場合は

public static IEnumerable<T> Leafs<T>(this ITreeNode<T> self) where T : ITreeNode<T> {
    return self.Traverse(
        a => a.Children, 
        (a, b, c) => (b.Any() ? b : new T?[1] { a }).Concat(c));
}

となります。

DescendArrivals

public static IEnumerable<T> DescendArrivals<T, Trc>(this ITreeNode<T> self, Func<T, Trc> selector, IEnumerable<Trc> trace, IEqualityComparer<Trc>? comparer = null) 
    where T : ITreeNode<T> ;

public static IEnumerable<T> DescendArrivals<T>(this ITreeNode<T> self,Func<T,bool> predicate)
    where T:ITreeNode<T>;

使用例:

A  
├ B  
│ ├ D  
│ │ ├ H  
│ │ └ I  
│ └ E  
│   ├ J  
│   └ K  
└ b  
  ├ F  
  └ d  
var nodes = root.DescendArrivals(
    x => x.Name, 
    new string[] { "A", "B", "D" },
    Equality<string>.ComparerBy(x=>x.ToUpper()));

Console.WriteLine(string.Join(",", nodes.Select(x => x.Name)));
// D,d

DescendTraces

public static IReadOnlyList<IEnumerable<T>> DescendTraces<T,Trc>(this ITreeNode<T> self,Func<T,Trc> selector,IEnumerable<Trc> trace,IEqualityComparer<Trc>? comparer = null) 
    where T : ITreeNode<T> ;

public static IReadOnlyList<IEnumerable<T>> DescendTraces<T>(this ITreeNode<T> self, Func<T, bool> predicate)
    where T:ITreeNode<T>;

使用例:

var nodetraces = root.DescendTraces(
    x => x.Name, 
    new string[] { "A", "B", "D" }, 
    Equality<string>.ComparerBy(x => x.ToUpper()));

foreach(var trace in nodetraces)
	Console.WriteLine(string.Join(",", trace.Select(x => x.Name)));
// A,B,D
// A,b,d

移動

編集

パラメータ/判定

変換

組立

⚠️ **GitHub.com Fallback** ⚠️