C# でツリー構造をトラバースする再帰ラムダ式を実装する方法を教えてください。
4 に答える
よし、ようやく空き時間を見つけた。
どうぞ:
class TreeNode
{
public string Value { get; set;}
public List<TreeNode> Nodes { get; set;}
public TreeNode()
{
Nodes = new List<TreeNode>();
}
}
Action<TreeNode> traverse = null;
traverse = (n) => { Console.WriteLine(n.Value); n.Nodes.ForEach(traverse);};
var root = new TreeNode { Value = "Root" };
root.Nodes.Add(new TreeNode { Value = "ChildA"} );
root.Nodes[0].Nodes.Add(new TreeNode { Value = "ChildA1" });
root.Nodes[0].Nodes.Add(new TreeNode { Value = "ChildA2" });
root.Nodes.Add(new TreeNode { Value = "ChildB"} );
root.Nodes[1].Nodes.Add(new TreeNode { Value = "ChildB1" });
root.Nodes[1].Nodes.Add(new TreeNode { Value = "ChildB2" });
traverse(root);
適切な解決策、そして実際に多くの関数型プログラミング言語で慣用的な解決策は、固定小数点コンビネータの使用です。簡単に言うと、固定小数点コンビネータは、「無名関数を再帰的に定義するにはどうすればよいですか?」という質問に答えます。しかし、解決策は非常に重要であるため、記事全体がそれらを説明するために書かれています.
単純で実用的な代替手段は、定義前の C: 宣言のふざけた態度に「時間をさかのぼる」ことです。以下を試してください (「factorial」関数):
Func<int, int> fact = null;
fact = x => (x == 0) ? 1 : x * fact(x - 1);
魅力のように機能します。
または、その子を適切にTreeNode
実装するクラスのオブジェクトの事前注文ツリー トラバーサルの場合:IEnumerable<TreeNode>
Action<TreeNode, Action<TreeNode>> preorderTraverse = null;
preorderTraverse = (node, action) => {
action(node);
foreach (var child in node) preorderTraverse(child, action);
};
簡単な代替手段は、C および C++ のふざけた態度に「時間をさかのぼる」ことです。つまり、定義の前に宣言を行います。次のことを試してください。
Func<int, int> fact = null; fact = x => (x == 0) ? 1 : x * fact(x - 1);
魅力のように機能します。
はい、それは機能しますが、1 つ注意点があります。C# には変更可能な参照があります。したがって、誤って次のようなことをしないように注意してください。
Func<int, int> fact = null;
fact = x => (x == 0) ? 1 : x * fact(x - 1);
// Make a new reference to the factorial function
Func<int, int> myFact = fact;
// Use the new reference to calculate the factorial of 4
myFact(4); // returns 24
// Modify the old reference
fact = x => x;
// Again, use the new reference to calculate
myFact(4); // returns 12
もちろん、この例は少し不自然ですが、これは変更可能な参照を使用している場合に発生する可能性があります。akuのリンクからのコンビネータを使用する場合、これは不可能です。
階層を表す Children コレクションを含む神話上のオブジェクト TreeItem を想定します。
public void HandleTreeItems(Action<TreeItem> item, TreeItem parent)
{
if (parent.Children.Count > 0)
{
foreach (TreeItem ti in parent.Children)
{
HandleTreeItems(item, ti);
}
}
item(parent);
}
これを呼び出して、1 つのアイテムを処理するラムダを渡し、その名前をコンソールに出力します。
HandleTreeItems(item => { Console.WriteLine(item.Name); }, TreeItemRoot);