dwayne
dwayne

Reputation: 13

Remove element from an array of objects while iterating

I have a list of id/parents that i'm iterating through an external extension. This list can have around 11 000 lines and that's why i need to remove some elements so i will just display the ones i need.

List of elements:

FlatData[] elements = new FlatData[]
{
   new FlatData {Id = 3, ParentId = 1, Text = "A"}, 
   new FlatData {Id = 4, ParentId = 1, Text = "D"},
   new FlatData {Id = 5, ParentId = 2, Text = "E"},
   new FlatData {Id = 7, ParentId = 2, Text = "G"},
   new FlatData {Id = 8, ParentId = 4, Text = "H"},
   new FlatData {Id = 9, ParentId = 8, Text = "H"},
   new FlatData {Id = 10, ParentId = 3, Text = "I"},
   new FlatData {Id = 11, Text = "I"},
};

While i'm iterating, i want to remove some elements so they will not be processed or displayed, but the element i want to remove still present in the output !

Here's the code iterating trough the elements:

int firstDepth = 0;

IEnumerable <DeepNodeData> nodes = elements.Where(x => x.Id >= 5).RecursiveJoin(element => element.Id,
     element => element.ParentId,
    (FlatData element, int index, int depth, IEnumerable<DeepNodeData> children) =>
     {
          int  position;

          if(depth == 0){
            firstDepth++;
          }

          if(firstDepth > 0){
              position= Array.FindIndex(elements, row => row.Id == index);
              elements.Skip(position);

             // or much better, exit the program with something like break ?;
          }

          return new DeepNodeData()
          {
              Id = element.Id,
              Index = index,
              Text = element.Text,
              Children = children
          };
    });

I only know the starting position (the id = 4) which is the root parent. I will only know the positions of the elements i want to remove once i have the depth value. The idea is to display only the childrens attached to the id = 4. Here's the elements i should have at the end and that should be processed to built my treeview:

FlatData[] elements = new FlatData[]
 {
     new FlatData {Id = 4, ParentId = 1, Text = "D" },
     new FlatData {Id = 8, ParentId = 4, Text = "H" },
     new FlatData {Id = 9, ParentId = 8, Text = "H" },
 };

The final output should be this :

    [
    {
    "id": 4,
    "index": 1,
    "depth": 0,
    "parentId": 0,
    "text": "D",
    "children": [
       {
       "id": 8,
       "index": 1,
       "depth": 1,
       "parentId": 0,
       "text": "H",
       "children": [
          {
           "id": 9,
           "index": 1,
           "depth": 2,
           "parentId": 0,
            "text": "H",
            "children": []
          }
        ]
      }
    ]
   }
  ]

The recursive extension :

public static IEnumerable<TResult> RecursiveJoin<TSource, TKey, TResult>(this 

    IEnumerable<TSource> source,
         Func<TSource, TKey> parentKeySelector,
         Func<TSource, TKey> childKeySelector,
         Func<TSource, int, int, IEnumerable<TResult>, TResult> resultSelector,
         IComparer<TKey> comparer)
      {
        // prevent source being enumerated more than once per RecursiveJoin call
        source = new LinkedList<TSource>(source);

        // fast binary search lookup
        SortedDictionary<TKey, TSource> parents = new SortedDictionary<TKey, TSource>(comparer);
        SortedDictionary<TKey, LinkedList<TSource>> children
           = new SortedDictionary<TKey, LinkedList<TSource>>(comparer);

        foreach (TSource element in source)
        {
          parents[parentKeySelector(element)] = element;

          LinkedList<TSource> list;

          TKey childKey = childKeySelector(element);

          if (!children.TryGetValue(childKey, out list))
          {
            children[childKey] = list = new LinkedList<TSource>();
          }

          list.AddLast(element);
        }

        // initialize to null otherwise compiler complains at single line assignment
        Func<TSource, int, IEnumerable<TResult>> childSelector = null;

        childSelector = (TSource parent, int depth) =>
        {
          LinkedList<TSource> innerChildren = null;

          if (children.TryGetValue(parentKeySelector(parent), out innerChildren))
          {
            return innerChildren.Select((child, index)
               => resultSelector(child, index, depth , childSelector(child, depth + 1)));
          }
          return Enumerable.Empty<TResult>();
        };

        return source.Where(element => !parents.ContainsKey(childKeySelector(element)))
           .Select((element, index) => resultSelector(element, index, 0 ,childSelector(element, 1)));
      }

Upvotes: 0

Views: 178

Answers (1)

John Wu
John Wu

Reputation: 52230

Instead of

.Where(x => x.Id >= 5)

try

.Where(x => x.Id >= 5 && x.Id != 11)

If you don't know the ID but know the index (offset into the list), you can get the index using an alternative overload of Where, which supplies the index to the where delegate as a second argument.

    .Where
    (
        (row, index) => row.Id >  5  //Filter on data
                     && index  != 11 //Filter on row index
    )

or you could simply do this (which is slightly less efficient):

.Where(x => x.Id >= 5 && x.Id != elements[11].Id)

If (based on your edits) you are looking for a list of children and grandchildren, given a parent ID, you can use a method like this one to search the tree:

public static class ExtensionMethods
{
    public static IEnumerable<FlatData> GetDescendents(this IEnumerable<FlatData> This, int rootId)
    {
        var rootItem = This.Single( x => x.Id == rootId );
        var queue = new Queue<FlatData>( new [] { rootItem } );
        while (queue.Count > 0)
        {
            var item = queue.Dequeue();
            yield return item;
            foreach (var child in This.Where( x => x.ParentId == item.Id )) 
            {
                queue.Enqueue(child);
            }
        }
    }
}

Then use

var filtered = elements.GetDescendents(4);

Example on DotNetFiddle

If you need to limit the levels, you can try this approach, which is less efficient but makes it clear which level each child is at and when to stop the search:

public static IEnumerable<FlatData> GetDescendents(this IEnumerable<FlatData> This, int rootId, int maxDepth)
{
    var results = Enumerable.Range(0, maxDepth+1 ).Select( i => new List<FlatData>() ).ToList();
    results[0].Add
    (
        This.Single( x => x.Id == rootId ) 
    );
    for (int level = 1; level <= maxDepth; level++)
    {
        results[level].AddRange
        (
            results[level-1].SelectMany
            ( 
                x => This.Where( y => y.ParentId == x.Id )
            )
        );
    }
    return results.SelectMany( x => x );
}

Example on DotNetFiddle

Upvotes: 2

Related Questions