本質的にIEnumerableではないコレクションを列挙しますか?
-
06-07-2019 - |
質問
階層オブジェクトを再帰的に列挙し、いくつかの基準に基づいていくつかの要素を選択する場合、<!> quot; flattening <!> quot;などのテクニックの例が多数あります。次に、Linqを使用してフィルタリングします。ここにあるようなものです。
しかし、フォームのControlsコレクションやTreeViewのNodesコレクションのようなものを列挙するとき、これらのタイプのテクニックは(拡張メソッドへの)引数を必要とするため使用できません。 IEnumerableコレクションです。SomeForm.Controlsを渡してもコンパイルされません。
私が見つけた最も有用なものはこれでした:
Control.ControlCollectionの拡張メソッドを提供し、IEnumerableの結果を提供します。これにより、Linqで使用できます。
上記の例を修正して、TreeViewのノードを問題なく解析しました。
public static IEnumerable<TreeNode> GetNodesRecursively(this TreeNodeCollection nodeCollection)
{
foreach (TreeNode theNode in nodeCollection)
{
yield return theNode;
if (theNode.Nodes.Count > 0)
{
foreach (TreeNode subNode in theNode.Nodes.GetNodesRecursively())
{
yield return subNode;
}
}
}
}
これは、現在拡張メソッドを使用して記述している種類のコードです。
var theNodes = treeView1.Nodes.GetNodesRecursively();
var filteredNodes =
(
from n in theNodes
where n.Text.Contains("1")
select n
).ToList();
そして、制約が渡される場合、これを行うよりエレガントな方法があるかもしれないと思います。
このようなプロシージャを一般的に定義できるかどうかを知りたいので、実行時にコレクションのタイプと実際のコレクションを一般的なパラメータに渡すことができるので、コードはTreeNodeCollectionかControls.Collectionかには関係ありません。
また、Linqが使用できる形式でTreeNodeCollectionまたはControl.ControlCollectionを取得するために、2番目のリンク(上記)に示されている方法以外の方法(安い?fastser?)があるかどうかを知ることにも興味があります。
最初にリンクされたSO投稿のSelectMany(上記)に関するLeppieのコメントは、手がかりのようです。
SelectManyを使用した私の実験は、<!> quot; disasters。<!> quot;と呼んでいます。 :)
ポインターを高く評価します。私はこれらの領域に触れたすべてのSOポストを数時間読んで、<!> quot; y-combinator。<!> quot;のような異国情緒に向かって歩き回った。 <!> quot;謙bling <!> quot;経験、私は追加するかもしれません:)
解決
このコードはトリックを行う必要があります
public static class Extensions
{
public static IEnumerable<T> GetRecursively<T>(this IEnumerable collection,
Func<T, IEnumerable> selector)
{
foreach (var item in collection.OfType<T>())
{
yield return item;
IEnumerable<T> children = selector(item).GetRecursively(selector);
foreach (var child in children)
{
yield return child;
}
}
}
}
使用方法の例は次のとおりです
TreeView view = new TreeView();
// ...
IEnumerable<TreeNode> nodes = view.Nodes.
.GetRecursively<TreeNode>(item => item.Nodes);
更新: Eric Lippertの投稿への返信。
public static class Extensions
{
public static IEnumerable<T> GetItems<T>(this IEnumerable collection,
Func<T, IEnumerable> selector)
{
Stack<IEnumerable<T>> stack = new Stack<IEnumerable<T>>();
stack.Push(collection.OfType<T>());
while (stack.Count > 0)
{
IEnumerable<T> items = stack.Pop();
foreach (var item in items)
{
yield return item;
IEnumerable<T> children = selector(item).OfType<T>();
stack.Push(children);
}
}
}
}
次のベンチマーク手法を使用して、簡単なパフォーマンステストを行いました。結果はそれ自体を物語っています。ツリーの深さは、2番目のソリューションのパフォーマンスにわずかな影響しか与えません。一方、最初のソリューションではパフォーマンスが急速に低下し、最終的にはツリーの深さが大きくなりすぎるとStackOverflowException
になります。
他のヒント
あなたは正しい道を進んでいるようで、上記の答えにはいくつかの良いアイデアがあります。しかし、これらの再帰的ソリューションにはすべて、いくつかの深い欠陥があることに注意してください。
問題のツリーが最大nのノードを持ち、最大ツリー深度がd <!> lt; = nであるとします。
まず、ツリーの深さでシステムスタックスペースを消費します。ツリー構造が非常に深い場合、スタックが破壊され、プログラムがクラッシュする可能性があります。ツリーの深さdは、ツリーの分岐係数に応じてO(lg n)です。最悪の場合はまったく分岐しません。リンクリストだけです。この場合、数百ノードしかないツリーがスタックを破壊します。
第二に、ここでやっていることは、イテレータを呼び出すイテレータを呼び出すイテレータを構築することです...そのため、一番上のイテレータ上のすべてのMoveNext()は、実際には再びO(d)コスト。すべてのノードでこれを行う場合、コールの合計コストはO(nd)であり、これは最悪の場合O(n ^ 2)および最良の場合O(n lg n)です。あなたは両方より良いことができます。これが時間的に線形になれない理由はありません。
コツは、小さくて壊れやすいシステムスタックの使用を停止して、次に何をすべきかを追跡し、ヒープに割り当てられたスタックを使用して明示的に追跡を開始することです。
このリストについて、Wes Dyerの記事を読書リストに追加する必要があります。
https://blogs.msdn。 microsoft.com/wesdyer/2007/03/23/all-about-iterators/
彼は最後に再帰反復子を書くためのいくつかの良いテクニックを提供します。
TreeNodesについてはわかりませんが、System.Linq
などを使用して、フォームのControlsコレクションをIEnumerableにすることができます。たとえば、
var ts = (from t in this.Controls.OfType<TextBox>
where t.Name.Contains("fish")
select t);
//Will get all the textboxes whose Names contain "fish"
申し訳ありませんが、これを再帰的にする方法がわかりません。頭から離してください。
mridengrenのソリューションに基づく:
public static IEnumerable<T> GetRecursively<T>(this IEnumerable collection,
Func<T, IEnumerable> selector,
Func<T, bool> predicate)
{
foreach (var item in collection.OfType<T>())
{
if(!predicate(item)) continue;
yield return item;
IEnumerable<T> children = selector(item).GetRecursively(selector, predicate);
foreach (var child in children)
{
yield return child;
}
}
}
var theNodes = treeView1.Nodes.GetRecursively<TreeNode>(
x => x.Nodes,
n => n.Text.Contains("1")).ToList();
編集:BillWの場合
このようなことを求めていると思います。
public static IEnumerable<T> <T,TCollection> GetNodesRecursively(this TCollection nodeCollection, Func<T, TCollection> getSub)
where T, TCollection: IEnumerable
{
foreach (var theNode in )
{
yield return theNode;
foreach (var subNode in GetNodesRecursively(theNode, getSub))
{
yield return subNode;
}
}
}
var all_control = GetNodesRecursively(control, c=>c.Controls).ToList();