1

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)

5
  • 2
    what did you attempt? and where are you stuck? Commented Feb 28, 2016 at 11:48
  • I need an actual implementation of getAllHiearchyObject(Tag parent) method. Commented Feb 28, 2016 at 11:53
  • Do the ParentTag and ChildTag properties have values? The example tags you provided don't seem to be correct. For example the Child's ParentId should be 1, isn't it? And those previously mentioned properties are all null. If you have those filled out you can just loop through the hierarchy with a while loop. Commented Feb 28, 2016 at 12:19
  • I am actualy querying database with lazy loading, and proper id includes children. It's just an example with some mistakes but idea i think is obvious Commented Feb 28, 2016 at 12:35
  • More of a recursion problem rather than a simple O(n) walking of a list... aaaand I don't see you using any recursion here... ;-) Commented Feb 28, 2016 at 14:16

2 Answers 2

3

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))
...
Sign up to request clarification or add additional context in comments.

4 Comments

what about grand grand children?
This should recursively traverse n levels deep. See the nested/recursive call to FlattenTag?
I've simple solved the problem inside while loop. while (true) bla bla... Your code is a bit hard to understand for me
Sorry this is confusing to you, I will try to clarify if you can suggest what part is confusing.
1

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

4 Comments

i have many to many parent-child relationship but i am doing it more simpler way
@user3857731 I have added proper tree handling (parent to many children).
I've simple solved the problem inside while loop. while (true) bla bla... Your code is a bit hard to understand for me
@user3857731 Perhaps it is a bit too generic, but it works and it is very generic, so you can adapt it easily to any other similar situation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.