Я, наверное, мог бы написать это сам, но конкретный способ, которым я пытаюсь это сделать, сбивает меня с толку. Я пытаюсь написать общий метод расширения, аналогичный другим, представленным в .NET 3.5, который возьмет вложенный IEnumerable из IEnumerables (и т. д.) И сведет его в один IEnumerable. У кого-нибудь есть идеи?
В частности, у меня проблемы с синтаксисом самого метода расширения, поэтому я могу работать над алгоритмом сглаживания.
Ничто не указывает на то, что это вопрос домашнего задания ...
Данные - это обычные объекты.
Программист-преступник: ничего явного, но когда речь идет о синтаксисе, а кода нет, шансы хорошие, это домашнее задание. (Но OP может его удалить.)
Актуально (решение Эрика Липперта со стеком для больших структур данных): stackoverflow.com/a/20335369/5962841





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;
}
}
Может вот так? Или вы имеете в виду, что он потенциально может быть бесконечно глубоким?
форум с удовольствием продолжает есть подпись, может быть, это сработает как комментарий. общедоступный статический IEnumerable <T> Flatten <T> (эта последовательность IEnumerable <IEnumerable <T>>)
Вот и все. Он задыхался от кода и предварительных тегов (ни то, ни другое не нужно).
Обратите внимание, что из-за общей инвариантности вы не сможете передать, скажем, List <List <int>> этому методу - List <List <int>> не является List <IEnumerable <int>>. Вот почему у меня в похожем коде есть два параметра типа. Конечно, это может измениться с C# 4 :)
По сути, вам нужно иметь мастер IENumerable, который находится за пределами вашей рекурсивной функции, а затем в вашей рекурсивной функции (псевдо-код)
private void flattenList(IEnumerable<T> list)
{
foreach (T item in list)
{
masterList.Add(item);
if (item.Count > 0)
{
this.flattenList(item);
}
}
}
Хотя я действительно не уверен, что вы имеете в виду под IEnumerable, вложенным в IEnumerable ... что внутри этого? Сколько уровней вложенности? Какой последний тип? очевидно, что мой код неверен, но я надеюсь, что это заставит вас задуматься.
Хм ... Я не уверен, точно, что вы хотите здесь, но вот вариант "одного уровня":
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 to 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;
}
}
}
Обратите внимание, что это будет обрабатывать строку как последовательность символов, однако, вы можете захотеть, чтобы строки особого случая были отдельными элементами, а не сглаживали их, в зависимости от вашего варианта использования.
Это поможет?
Разве не для этого предназначен [SelectMany] [1]?
enum1.SelectMany(
a => a.SelectMany(
b => b.SelectMany(
c => c.Select(
d => d.Name
)
)
)
);
Это не полностью рекурсивно, оно обрабатывает только предопределенное количество уровней, в этом случае 3, истинная рекурсия может быть неограниченным количеством уровней ...
Вот расширение, которое может помочь. Он пройдет по всем узлам в вашей иерархии объектов и выберет те, которые соответствуют критериям. Предполагается, что каждый объект в вашей иерархии имеет свойство коллекции содержит его дочерние объекты.
/// 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);
}
}
И способ получения трехуровневой глубокой иерархии узлов
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
@Marc Спасибо за редактирование! @lukevenediger, вы можете использовать расширение Map с такими вещами, как коллекция TreeNode для Treeview, если вы сначала получите коллекцию в форме, совместимой с IEnumerable, например: IEnumerable <TreeNode> theNodes1 = treeView2.Nodes.OfType <TreeNode> (); ... или ... IEnumerable <TreeNode> theNodes2 = treeView2.Nodes.Cast <TreeNode> () .Select (node => node);
Небольшое примечание, но способ, которым значения возвращаются в модульных тестах, мог бы быть более понятным. Вы можете назвать его так: nodes.Map(i => true, n => n.Children). Я бы также не советовал называть этот метод расширения «Map», «Flatten» - более четкое описание.
Вот измененный Ответ Джона Скита, позволяющий использовать более "одного уровня":
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#.
То же и в Python:
#!/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
Поскольку yield недоступен в 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)
If(objects.Any()) Then
Return objects.Union(objects.Select(selector).Where(e => e != null).SelectMany(e => e)).Flatten(selector))
Else
Return objects
End If
End Function
public static class Extensions{
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> objects, Func<T, IEnumerable<T>> selector) where T:Component{
if (objects.Any()){
return objects.Union(objects.Select(selector).Where(e => e != null).SelectMany(e => e).Flatten(selector));
}
return objects;
}
}
отредактировал включить:
Я подумал, что поделюсь полным примером с обработкой ошибок и подходом с единой логикой.
Рекурсивное выравнивание так же просто, как:
Версия 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 в 1-й версииif (source != null) перед foreach во 2-й версии.EmptyIfNull() в первой версии - обратите внимание, что SelectMany завершится ошибкой, если селектор вернет nullif (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));
}
}
Спасибо. Мне не хватало только части !source.Any() ? source в моей собственной версии
Мне пришлось реализовать свое с нуля, потому что все предоставленные решения сломались бы в случае возникновения цикла, то есть дочернего элемента, указывающего на своего предка. Если у вас такие же требования, как у меня, взгляните на это (также дайте мне знать, не сломается ли мое решение при каких-либо особых обстоятельствах):
Как использовать:
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);
}
}
Функция:
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();
Хорошо, вот еще одна версия, которая объединена примерно из 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);
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();
Как представлены ваши данные? Это не так просто, как с XPath.