Recursive List Flattening

后端 未结 13 1331
既然无缘
既然无缘 2020-11-27 04:25

I could probably write this myself, but the specific way I\'m trying to accomplish it is throwing me off. I\'m trying to write a generic extension method similar to the oth

13条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-11-27 05:27

    Here's an extension that might help. It will traverse all nodes in your hierarchy of objects and pick out the ones that match a criteria. It assumes that each object in your hierarchy has a collection property that holds its child objects.

    Here's the extension:

    /// Traverses an object hierarchy and return a flattened list of elements
    /// based on a predicate.
    /// 
    /// TSource: The type of object in your collection.
    /// source: The collection of your topmost TSource objects.
    /// 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 Map(
      this IEnumerable source,
      Func selectorFunction,
      Func> 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;
    }
    

    Examples (Unit Tests):

    First we need an object and a nested object hierarchy.

    A simple node class

    class Node
    {
      public int NodeId { get; set; }
      public int LevelId { get; set; }
      public IEnumerable Children { get; set; }
    
      public override string ToString()
      {
        return String.Format("Node {0}, Level {1}", this.NodeId, this.LevelId);
      }
    }
    

    And a method to get a 3-level deep hierarchy of nodes

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

    First Test: flatten the hierarchy, no filtering

    [Test]
    public void Flatten_Nested_Heirachy()
    {
      IEnumerable 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());
    }
    

    This will show:

    Node 1, Level 1
    Node 6, Level 1
    Node 2, Level 2
    Node 3, Level 2
    Node 4, Level 3
    Node 5, Level 3
    

    Second Test: Get a list of nodes that have an even-numbered NodeId

    [Test]
    public void Only_Return_Nodes_With_Even_Numbered_Node_IDs()
    {
      IEnumerable 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());
    }
    

    This will show:

    Node 6, Level 1
    Node 2, Level 2
    Node 4, Level 3
    

提交回复
热议问题