user3857731
user3857731

Reputation: 649

C# linq hierarchy tree

Tag class consists of ID Name and List<Tagging> :

public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Tagging> Tagging { get; set; }
}

Tagging class :

public class Tagging
{
    public int Id { get; set; }

    [ForeignKey("ParentTag")]
    public int ParentId { get; set; }
    public Tag ParentTag { get; set; }

    [ForeignKey("ChildTag")]
    public int ChildId { get; set; }
    public Tag ChildTag { get; set; }
}

Tagging class just express many to many relationship between tags, for hierarchical purpose. For example given a list :

List<Tag> tags = new List<Tag>();
var parent = new Tag {Name = "Parent", Id = 1, Tagging = new List<Tagging>{ new Tagging{ ParentId = 1, ChildId = 2}}};
var child = new Tag {Name = "Child", Id = 2, Tagging = new List<Tagging> { new Tagging { ParentId = 2, ChildId = 3 }}};
var grandChild = new Tag {Name = "GrandChild", Id = 3};

tags.Add(parent);
tags.Add(child);
tags.Add(grandChild);

I am trying to loop through all hierarchical objects connected to his parent. For example if you call a method getAllHiearchyObject(Tag parent) Output should be something like this :

Name : "Parent", Id = 1;
Name : "Child", Id : 2;
Name : "GrandChild", Id :3 

I need an actual implementation of getAllHiearchyObject(Tag parent)

Upvotes: 1

Views: 840

Answers (2)

blins
blins

Reputation: 2535

How about this...

static IEnumerable<Tag> FlattenTag(Tag root)
{
    yield return root;

    if (root.Tagging != null)
        foreach (var childTagging in root.Tagging)
            if (childTagging.ChildTag != null)
                foreach (var grandChildTag in FlattenTag(childTagging.ChildTag))
                    yield return grandChildTag;
}

Note that the second foreach above allows for the use of yield with recursion.

Usage...

foreach(var tag in FlattenTag(root))
...

Upvotes: 3

Eugene Podskal
Eugene Podskal

Reputation: 10401

Only one parent to one child.

For a simple case when you have only one parent-child relationship you can create methods like:

public static class EnumerableExtensions
{
    #region Methods

    public static IEnumerable<T> Unwind<T>(T first, Func<T, T> getNext) 
        where T : class
    {
        if (getNext == null)
            throw new ArgumentNullException(nameof(getNext));

        return Unwind(
            first: first,
            getNext: getNext,
            isAfterLast: item => 
                item == null);
    }

    public static IEnumerable<T> Unwind<T>(
        T first,
        Func<T, T> getNext,
        Func<T, Boolean> isAfterLast)        
    {
        if (getNext == null)
            throw new ArgumentNullException(nameof(getNext));
        if (isAfterLast == null)
            throw new ArgumentNullException(nameof(isAfterLast));

        var current = first;
        while(!isAfterLast(current))
        {
            yield return current;
            current = getNext(current);
        }
    }

    #endregion
}

And use them in the following way (I have set ChildTag in Taggings, as it will be done by EF):

List<Tag> tags = new List<Tag>();
var grandChild = new Tag { Name = "GrandChild", Id = 3 };
var child = new Tag { Name = "Child", Id = 2, Tagging = new List<Tagging> { new Tagging { ParentId = 2, ChildId = 3, ChildTag = grandChild } } };
var parent = new Tag { Name = "Parent", Id = 1, Tagging = new List<Tagging> { new Tagging { ParentId = 1, ChildId = 2, ChildTag = child } } };

tags.Add(parent);
tags.Add(child);
tags.Add(grandChild);

var fromParent = EnumerableExtensions
    .Unwind(
        parent,
        item =>
            item?.Tagging?.FirstOrDefault()?.ChildTag)
    .ToArray();
Console.WriteLine("Parent to child:");
foreach (var item in fromParent)
{
    Console.WriteLine(item);
}

Proper parent to many children

For a proper tree creation you will have to use:

public class UnwoundItem<T> : IEnumerable<UnwoundItem<T>>
{
    private readonly T _item;
    private readonly IEnumerable<UnwoundItem<T>> _unwoundItems;

    public UnwoundItem(T item, IEnumerable<UnwoundItem<T>> unwoundSubItems)
    {
        this._item = item;
        this._unwoundItems = unwoundSubItems ?? Enumerable.Empty<UnwoundItem<T>>();
    }

    public T Item
    {
        get
        {
            return this._item;
        }
    }

    public IEnumerable<UnwoundItem<T>> UnwoundSubItems
    {
        get
        {
            return this._unwoundItems;
        }
    }

    public IEnumerator<UnwoundItem<T>> GetEnumerator()
    {
        return this._unwoundItems.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

and

public static class EnumerableExtensions
{
    #region Methods

    public static UnwoundItem<T> UnwindMany<T>(
        T first,
        Func<T, IEnumerable<T>> getNext)
        where T : class
    {
        if (getNext == null)
            throw new ArgumentNullException(nameof(getNext));

        return UnwindMany(
            first: first,
            getNext: getNext,
            isAfterLast: collection =>
                collection == null);
    }

    public static UnwoundItem<T> UnwindMany<T>(
        T first,
        Func<T, IEnumerable<T>> getNext,
        Func<IEnumerable<T>, Boolean> isAfterLast)
    {
        if (getNext == null)
            throw new ArgumentNullException(nameof(getNext));
        if (isAfterLast == null)
            throw new ArgumentNullException(nameof(isAfterLast));

        var currentItems = getNext(first);
        if (isAfterLast(currentItems))
            return new UnwoundItem<T>(
                item: first, 
                unwoundSubItems: Enumerable.Empty<UnwoundItem<T>>());

        return new UnwoundItem<T>(
            item: first,
            unwoundSubItems: currentItems
                .Select(item => 
                    UnwindMany(
                        item, 
                        getNext, 
                        isAfterLast)));
    }

    #endregion
}

It can be tested with:

private static void Print<T>(IEnumerable<UnwoundItem<T>> items, Func<T, String> toString, Int32 level)
{
    var indent = new String(' ', level * 4);
    foreach (var item in items)
    {
        Console.Write(indent);
        Console.WriteLine(toString(item.Item));
        Print(item.UnwoundSubItems, toString, level + 1);
    }
}

...

var grandChild = new Tag { Name = "GrandChild", Id = 3 };
var grandChild2 = new Tag { Name = "GrandChild 2", Id = 33 };
var child = new Tag { Name = "Child", Id = 2, Tagging = new List<Tagging> { new Tagging { ParentId = 2, ChildId = 3, ChildTag = grandChild } } };
var child2 = new Tag { Name = "Child 2", Id = 22, Tagging = new List<Tagging> { new Tagging { ParentId = 2, ChildId = 33, ChildTag = grandChild2 } } };
var parent = new Tag { Name = "Parent", Id = 1,
    Tagging = new List<Tagging> {
        new Tagging { ParentId = 1, ChildId = 2, ChildTag = child },
        new Tagging { ParentId = 1, ChildId = 2, ChildTag = child2 } }
};

var fromParent = EnumerableExtensions
    .UnwindMany(
        parent,
        item =>
            item?.Tagging?.Select(tagging => tagging.ChildTag));

Console.WriteLine("Parent to child:");                
Print(new[] { fromParent }, item => item.Name, 0);

Upvotes: 1

Related Questions