본질적으로 ienumerable이 아닌 컬렉션을 열거 하는가?
-
06-07-2019 - |
문제
일부 기준을 기반으로 일부 요소를 선택하고 계층 적 객체를 재귀 적으로 열거하려면 "평평한"과 같은 기술의 예제가 여기에있는 것과 마찬가지로 LINQ를 사용하여 필터링합니다.
그러나 양식의 Controls 모음 또는 TreeView의 노드 모음과 같은 것을 열거 할 때,이 유형의 기술을 사용할 수 없었습니다. 수집 : someform.controls를 통과하는 것은 컴파일되지 않습니다.
내가 찾은 가장 유용한 것은 다음과 같습니다.
Ienumerable 결과를 가진 ControlCollection을위한 확장 방법을 제공합니다. 그런 다음 LINQ와 함께 사용할 수 있습니다.
위의 예제를 수정하여 문제없이 트리 뷰의 노드를 구문 분석했습니다.
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입니다.
또한 두 번째 링크 (위)에 표시된 것보다 다른 방법 (위의)에 표시된 다른 방법이 있는지 아는 것이 관심이 있습니다.
Leppie의 'Selectmany에 대한 SOLES (위)에 링크 된 게시물에 대한 의견은 단서처럼 보입니다.
Selectmany에 대한 나의 실험은 다음과 같습니다. 글쎄, 그들을 "재해"라고 부릅니다. :)
포인터를 감사합니다. 나는 모든 포스트를 읽는 데 몇 시간을 보냈다. 나는이 영역에 대한 접촉을 발견하고 "y-combinator"와 같은 이국에 들어가는 것을 발견 할 수있다. "겸손한"경험, 나는 추가 할 수 있습니다 :)
해결책
이 코드는 트릭을 수행해야합니다
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);
}
}
}
}
다음을 사용하여 간단한 성능 테스트를했습니다 벤치마킹 기술. 결과는 스스로를 말합니다. 나무의 깊이는 두 번째 솔루션의 성능에 한계적인 영향을 미칩니다. 첫 번째 솔루션의 경우 성능이 빠르게 감소하고 결국에는 StackOverflowException
나무의 깊이가 너무 커질 때.
다른 팁
당신은 올바른 길을 가고있는 것 같고 위의 답변에는 좋은 아이디어가 있습니다. 그러나이 모든 재귀 솔루션에는 약간의 결함이 있습니다.
문제의 트리에 최대 트리 깊이가 d <= n 인 총 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에 대해서는 잘 모르겠지만 사용하여 Ienumerable 형식의 컨트롤 모음을 만들 수 있습니다. System.Linq
그리고 예를 들어
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"
내 머리 꼭대기 에서이 재귀를 만드는 방법을 모르겠다 고 말해서 죄송합니다.
Mrydengren의 솔루션을 기반으로 :
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();