문제
나는 아마 이것을 직접 쓸 수 있었지만, 내가 달성하려는 구체적인 방법은 나를 버리고있다. .NET 3.5에 도입 된 다른 확장 방법과 유사한 일반적인 확장 방법을 작성하려고합니다. 누구든지 아이디어가 있습니까?
구체적으로, 나는 평평한 알고리즘에서 작업 할 수 있도록 확장 메소드 자체의 구문에 문제가 있습니다.
해결책
흠 ... 잘 모르겠습니다 바로 그거죠 여기서 원하는 것, 그러나 여기에 "원 레벨"옵션이 있습니다.
public static IEnumerable<TElement> Flatten<TElement,TSequence> (this IEnumerable<TSequence> sequences)
where TSequence : IEnumerable<TElement>
{
foreach (TSequence sequence in sequences)
{
foreach(TElement element in sequence)
{
yield return element;
}
}
}
그것이 당신이 원하는 것이 아니라면, 당신이 원하는 일의 서명을 제공 할 수 있습니까? 일반적인 형태가 필요하지 않고 LINQ에서 XML 생성자가하는 일을하고 싶다면 합리적으로 간단합니다. 반복자 블록의 재귀 적 사용은 비교적 비효율적입니다. 같은 것 :
static IEnumerable Flatten(params object[] objects)
{
// Can't easily get varargs behaviour with IEnumerable
return Flatten((IEnumerable) objects);
}
static IEnumerable Flatten(IEnumerable enumerable)
{
foreach (object element in enumerable)
{
IEnumerable candidate = element as IEnumerable;
if (candidate != null)
{
foreach (object nested in candidate)
{
yield return nested;
}
}
else
{
yield return element;
}
}
}
그러나 문자열을 숯의 시퀀스로 취급 할 것입니다. 사용 사례에 따라 특수 사례를 개별 요소가되기를 원할 수도 있습니다.
그게 도움이 되나요?
다른 팁
다음은 도움이 될 수있는 확장 기능이 있습니다. 객체의 계층 구조에서 모든 노드를 가로 지르고 기준과 일치하는 노드를 선택합니다. 그것은 당신의 계층 구조의 각 객체를 가정합니다 수집 속성이 있습니다 그것은 어린이 물체를 가지고 있습니다.
확장은 다음과 같습니다.
/// Traverses an object hierarchy and return a flattened list of elements
/// based on a predicate.
///
/// TSource: The type of object in your collection.</typeparam>
/// source: The collection of your topmost TSource objects.</param>
/// selectorFunction: A predicate for choosing the objects you want.
/// getChildrenFunction: A function that fetches the child collection from an object.
/// returns: A flattened list of objects which meet the criteria in selectorFunction.
public static IEnumerable<TSource> Map<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> selectorFunction,
Func<TSource, IEnumerable<TSource>> getChildrenFunction)
{
// Add what we have to the stack
var flattenedList = source.Where(selectorFunction);
// Go through the input enumerable looking for children,
// and add those if we have them
foreach (TSource element in source)
{
flattenedList = flattenedList.Concat(
getChildrenFunction(element).Map(selectorFunction,
getChildrenFunction)
);
}
return flattenedList;
}
예제 (단위 테스트) :
먼저 객체와 중첩 된 객체 계층이 필요합니다.
간단한 노드 클래스
class Node
{
public int NodeId { get; set; }
public int LevelId { get; set; }
public IEnumerable<Node> Children { get; set; }
public override string ToString()
{
return String.Format("Node {0}, Level {1}", this.NodeId, this.LevelId);
}
}
그리고 3 단계 깊은 계층 구조를 얻는 방법
private IEnumerable<Node> GetNodes()
{
// Create a 3-level deep hierarchy of nodes
Node[] nodes = new Node[]
{
new Node
{
NodeId = 1,
LevelId = 1,
Children = new Node[]
{
new Node { NodeId = 2, LevelId = 2, Children = new Node[] {} },
new Node
{
NodeId = 3,
LevelId = 2,
Children = new Node[]
{
new Node { NodeId = 4, LevelId = 3, Children = new Node[] {} },
new Node { NodeId = 5, LevelId = 3, Children = new Node[] {} }
}
}
}
},
new Node { NodeId = 6, LevelId = 1, Children = new Node[] {} }
};
return nodes;
}
첫 번째 테스트 : 필터링없이 계층 구조를 평평하게합니다
[Test]
public void Flatten_Nested_Heirachy()
{
IEnumerable<Node> nodes = GetNodes();
var flattenedNodes = nodes.Map(
p => true,
(Node n) => { return n.Children; }
);
foreach (Node flatNode in flattenedNodes)
{
Console.WriteLine(flatNode.ToString());
}
// Make sure we only end up with 6 nodes
Assert.AreEqual(6, flattenedNodes.Count());
}
이것은 보여줄 것입니다 :
Node 1, Level 1
Node 6, Level 1
Node 2, Level 2
Node 3, Level 2
Node 4, Level 3
Node 5, Level 3
두 번째 테스트 : Nodeid가 균일 한 노드 목록 얻기
[Test]
public void Only_Return_Nodes_With_Even_Numbered_Node_IDs()
{
IEnumerable<Node> nodes = GetNodes();
var flattenedNodes = nodes.Map(
p => (p.NodeId % 2) == 0,
(Node n) => { return n.Children; }
);
foreach (Node flatNode in flattenedNodes)
{
Console.WriteLine(flatNode.ToString());
}
// Make sure we only end up with 3 nodes
Assert.AreEqual(3, flattenedNodes.Count());
}
이것은 보여줄 것입니다 :
Node 6, Level 1
Node 2, Level 2
Node 4, Level 3
오류 처리와 단일 로그 인 Apporoach와 함께 완전한 예제를 공유 할 것이라고 생각했습니다.
재귀 평탄화는 다음과 같이 간단합니다.
LINQ 버전
public static class IEnumerableExtensions
{
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
return !source.Any() ? source :
source.Concat(
source
.SelectMany(i => selector(i).EmptyIfNull())
.SelectManyRecursive(selector)
);
}
public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> source)
{
return source ?? Enumerable.Empty<T>();
}
}
비 LINQ 버전
public static class IEnumerableExtensions
{
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
foreach (T item in source)
{
yield return item;
var children = selector(item);
if (children == null)
continue;
foreach (T descendant in children.SelectManyRecursive(selector))
{
yield return descendant;
}
}
}
}
디자인 결정
나는 결정했다 :
- 널의 평탄화를 허용하지 않습니다
IEnumerable
, 이것은 예외 던지기를 제거하고 다음을 변경할 수 있습니다.- 첨가
source = source.EmptyIfNull();
~ 전에return
첫 번째 버전에서 - 첨가
if (source != null)
~ 전에foreach
두 번째 버전에서
- 첨가
- 선택기가 널 컬렉션을 반환 할 수 있도록 허용 -이 방법으로 어린이 목록이 비어 있지 않도록 발신자의 책임을 제거하고 있습니다.
- 풀이
.EmptyIfNull()
첫 번째 버전에서는 주목하십시오SelectMany
선택기가 NULL을 반환하면 실패합니다 - 풀이
if (children == null) continue;
두 번째 버전에서는 주목하십시오foreach
널에 실패합니다IEnumerable
매개 변수
- 풀이
- 어린이를 필터링 할 수 있습니다
.Where
발신자 쪽 또는 내부의 조항 어린이 선택기 통과하기보다는 어린이 필터 선택기 매개 변수 :- 두 버전 모두에서 연기 된 호출이기 때문에 효율성에 영향을 미치지 않습니다.
- 다른 논리를 메소드와 혼합하고 논리를 분리하는 것을 선호합니다.
샘플 사용
화면에서 모든 컨트롤을 얻기 위해 LightSwitch 에서이 확장 방법을 사용하고 있습니다.
public static class ScreenObjectExtensions
{
public static IEnumerable<IContentItemProxy> FindControls(this IScreenObject screen)
{
var model = screen.Details.GetModel();
return model.GetChildItems()
.SelectManyRecursive(c => c.GetChildItems())
.OfType<IContentItemDefinition>()
.Select(c => screen.FindControl(c.Name));
}
}
selectmany] [1]이 무엇인가?
enum1.SelectMany(
a => a.SelectMany(
b => b.SelectMany(
c => c.Select(
d => d.Name
)
)
)
);
다음은 수정되었습니다 Jon Skeet의 대답 "한 레벨"이상을 허용하려면 :
static IEnumerable Flatten(IEnumerable enumerable)
{
foreach (object element in enumerable)
{
IEnumerable candidate = element as IEnumerable;
if (candidate != null)
{
foreach (object nested in Flatten(candidate))
{
yield return nested;
}
}
else
{
yield return element;
}
}
}
면책 조항 : 나는 C#을 모른다.
파이썬에서도 마찬가지입니다.
#!/usr/bin/env python
def flatten(iterable):
for item in iterable:
if hasattr(item, '__iter__'):
for nested in flatten(item):
yield nested
else:
yield item
if __name__ == '__main__':
for item in flatten([1,[2, 3, [[4], 5]], 6, [[[7]]], [8]]):
print(item, end=" ")
인쇄 :
1 2 3 4 5 6 7 8
기능:
public static class MyExtentions
{
public static IEnumerable<T> RecursiveSelector<T>(this IEnumerable<T> nodes, Func<T, IEnumerable<T>> selector)
{
if(nodes.Any())
return nodes.Concat(nodes.SelectMany(selector).RecursiveSelector(selector));
return nodes;
}
}
용법:
var ar = new[]
{
new Node
{
Name = "1",
Chilren = new[]
{
new Node
{
Name = "11",
Children = new[]
{
new Node
{
Name = "111",
}
}
}
}
}
};
var flattened = ar.RecursiveSelector(x => x.Children).ToList();
그만큼 SelectMany
확장 방법은 이미이 작업을 수행합니다.
시퀀스의 각 요소를 ienumerable <(of <(t>)>)에 투사하고 결과 시퀀스를 하나의 시퀀스로 평평하게합니다.
VB에서는 수율을 사용할 수 없으며 LINQ는 이연 실행과 간결한 구문을 모두 제공하므로 사용할 수도 있습니다.
<Extension()>
Public Function Flatten(Of T)(ByVal objects As Generic.IEnumerable(Of T), ByVal selector As Func(Of T, Generic.IEnumerable(Of T))) As Generic.IEnumerable(Of T)
Return objects.Union(objects.SelectMany(selector).Flatten(selector))
End Function
조상을 가리키는 어린이가있는 경우에 제공된 모든 솔루션이 중단 될 것이기 때문에 모든 솔루션이 깨질 것이기 때문에 나는 처음부터 광산을 구현해야했습니다. 내 요구 사항이 동일한 경우이를 살펴보십시오 (특별한 상황에서 내 솔루션이 깨질 수 있는지 알려주세요).
사용하는 방법:
var flattenlist = rootItem.Flatten(obj => obj.ChildItems, obj => obj.Id)
암호:
public static class Extensions
{
/// <summary>
/// This would flatten out a recursive data structure ignoring the loops. The end result would be an enumerable which enumerates all the
/// items in the data structure regardless of the level of nesting.
/// </summary>
/// <typeparam name="T">Type of the recursive data structure</typeparam>
/// <param name="source">Source element</param>
/// <param name="childrenSelector">a function that returns the children of a given data element of type T</param>
/// <param name="keySelector">a function that returns a key value for each element</param>
/// <returns>a faltten list of all the items within recursive data structure of T</returns>
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source,
Func<T, IEnumerable<T>> childrenSelector,
Func<T, object> keySelector) where T : class
{
if (source == null)
throw new ArgumentNullException("source");
if (childrenSelector == null)
throw new ArgumentNullException("childrenSelector");
if (keySelector == null)
throw new ArgumentNullException("keySelector");
var stack = new Stack<T>( source);
var dictionary = new Dictionary<object, T>();
while (stack.Any())
{
var currentItem = stack.Pop();
var currentkey = keySelector(currentItem);
if (dictionary.ContainsKey(currentkey) == false)
{
dictionary.Add(currentkey, currentItem);
var children = childrenSelector(currentItem);
if (children != null)
{
foreach (var child in children)
{
stack.Push(child);
}
}
}
yield return currentItem;
}
}
/// <summary>
/// This would flatten out a recursive data structure ignoring the loops. The end result would be an enumerable which enumerates all the
/// items in the data structure regardless of the level of nesting.
/// </summary>
/// <typeparam name="T">Type of the recursive data structure</typeparam>
/// <param name="source">Source element</param>
/// <param name="childrenSelector">a function that returns the children of a given data element of type T</param>
/// <param name="keySelector">a function that returns a key value for each element</param>
/// <returns>a faltten list of all the items within recursive data structure of T</returns>
public static IEnumerable<T> Flatten<T>(this T source,
Func<T, IEnumerable<T>> childrenSelector,
Func<T, object> keySelector) where T: class
{
return Flatten(new [] {source}, childrenSelector, keySelector);
}
}
좋아, 위의 약 3 개의 답변에서 결합 된 또 다른 버전이 있습니다.
재귀. 수율을 사용합니다. 일반적인. 선택적 필터 술어. 선택적 선택 기능. 내가 할 수있는만큼 간결합니다.
public static IEnumerable<TNode> Flatten<TNode>(
this IEnumerable<TNode> nodes,
Func<TNode, bool> filterBy = null,
Func<TNode, IEnumerable<TNode>> selectChildren = null
)
{
if (nodes == null) yield break;
if (filterBy != null) nodes = nodes.Where(filterBy);
foreach (var node in nodes)
{
yield return node;
var children = (selectChildren == null)
? node as IEnumerable<TNode>
: selectChildren(node);
if (children == null) continue;
foreach (var child in children.Flatten(filterBy, selectChildren))
{
yield return child;
}
}
}
용법:
// With filter predicate, with selection function
var flatList = nodes.Flatten(n => n.IsDeleted == false, n => n.Children);
static class EnumerableExtensions
{
public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> sequence)
{
foreach(var child in sequence)
foreach(var item in child)
yield return item;
}
}
어쩌면 이렇게? 아니면 잠재적으로 무의미하게 깊을 수 있다는 것을 의미합니까?
class PageViewModel {
public IEnumerable<PageViewModel> ChildrenPages { get; set; }
}
Func<IEnumerable<PageViewModel>, IEnumerable<PageViewModel>> concatAll = null;
concatAll = list => list.SelectMany(l => l.ChildrenPages.Any() ?
concatAll(l.ChildrenPages).Union(new[] { l }) : new[] { l });
var allPages = concatAll(source).ToArray();
기본적으로 재귀 기능 외부에있는 마스터 ienumerable이 필요하고 재귀 함수 (PSUedo-Code)가 필요합니다.
private void flattenList(IEnumerable<T> list)
{
foreach (T item in list)
{
masterList.Add(item);
if (item.Count > 0)
{
this.flattenList(item);
}
}
}
비록 나는 당신이 ienumerable에 둥지를 틀고 있다는 것이 무엇을 의미하는지 잘 모르겠지만 ... 그 안에있는 것은 무엇입니까? 몇 레벨의 중첩? 최종 유형은 무엇입니까? 분명히 내 코드는 정확하지 않지만, 당신이 생각하게되기를 바랍니다.