代码之家  ›  专栏  ›  技术社区  ›  Matt H

递归列表展平

  •  28
  • Matt H  · 技术社区  · 16 年前

    我也许可以自己写,但我试图实现它的具体方式是抛弃我。我正在尝试编写一个与.NET3.5中介绍的其他方法类似的通用扩展方法,该方法将接受IEnumerables的嵌套IEnumerable(等等),并将其展平为一个IEnumerable。有人有什么想法吗?

    具体地说,我在扩展方法本身的语法方面遇到了问题,因此我可以使用扁平化算法。

    13 回复  |  直到 15 年前
        1
  •  47
  •   Suzanne Soy    11 年前

    这里有一个扩展可能会有所帮助。它将遍历对象层次结构中的所有节点,并选择符合条件的节点。它假定层次结构中的每个对象 具有集合属性

    这是分机:

    /// 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
    
        2
  •  20
  •   Jon Skeet    16 年前

    确切地 您想要什么,但这里有一个“一级”选项:

    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;
            }
        }
    }
    

    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;
            }
        }
    }
    

    请注意,这将把字符串视为一个字符序列,但是-您可能希望将特殊情况下的字符串作为单个元素,而不是将它们展平,这取决于您的用例。

        3
  •  12
  •   marchewek    11 年前

    我想我应该和大家分享一个完整的错误处理示例和一个逻辑方法。

    递归展平非常简单,如下所示:

    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; 在第二个版本中,请注意 将在null上失败 数不清 参数
    • .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));
        }
    }
    
        4
  •  7
  •   Community T.Woody    7 年前

    这是一个修改过的 Jon Skeet's answer

    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;
            }
        }
    }
    

    #!/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 
    
        5
  •  6
  •   Mark Brackett Achilles Ram Nakirekanti    16 年前

    这不是[SelectMany][1]的作用吗?

    enum1.SelectMany(
        a => a.SelectMany(
            b => b.SelectMany(
                c => c.Select(
                    d => d.Name
                )
            )
        )
    );
    
        6
  •  3
  •   Yasin Kilicdere    3 年前

    功能:

    public static class MyExtentions
    {
        public static IEnumerable<T> RecursiveSelector<T>(this IEnumerable<T> nodes, Func<T, IEnumerable<T>> selector)
        {
            if(nodes.Any() == false)
            {
                return nodes; 
            }
    
            var descendants = nodes
                                .SelectMany(selector)
                                .RecursiveSelector(selector);
    
            return nodes.Concat(descendants);
        } 
    }
    

    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();
    
        7
  •  2
  •   Casey Plummer    6 年前

    好的,这是另一个版本,由上面的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);
    
        8
  •  1
  •   leppie    16 年前

    这个 SelectMany 扩展方法已经做到了这一点。

    将序列的每个元素投影到 将生成的序列展平为 一个序列。

        9
  •  1
  •   Aidin    10 年前

    我必须从头开始实现我的解决方案,因为如果有一个循环,即指向其祖先的子循环,所有提供的解决方案都会中断。如果您有与我相同的要求,请查看以下内容(同时请告知我的解决方案是否会在任何特殊情况下失效):

    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);
            }
        }
    
        10
  •  1
  •   Richard Collette    4 年前

    <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;
      }
    }
    

    包括:

        11
  •  0
  •   Derek Park    16 年前
    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;
        }
    }
    

    也许是这样?或者你的意思是说它可能非常深?

        12
  •  0
  •   FindOutIslamNow    6 年前
    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();
    
        13
  •  -1
  •   FlySwat    16 年前

    private void flattenList(IEnumerable<T> list)
    {
        foreach (T item in list)
        {
            masterList.Add(item);
    
            if (item.Count > 0)
            {
                this.flattenList(item);
            }
        }
    }
    

    虽然我真的不知道你说的IEnumerable嵌套在IEnumerable中是什么意思…里面是什么?有多少层嵌套?最后一种是什么?显然,我的代码是不正确的,但我希望它能让你思考。