-3

Hey Everyone, I am mentally stuck

 

I have a list of objects retrieved from a Web API that has three values that consist of a parent ID and a string value and a row ID

 

IE:

CategoryID         Name        ParentID

1                             Tools                   0

2                             Hammer              1

3                             ScrewDriver         1

4                             Phillips                 3

5                             Standard              3

6                             #2                      4

7                             Torx                    3

8                             #15                    7

 

etc.

This needs to be put into a simple list object that consists of a ParentID and a concatenated string of the immediate category name and the parent id

 

CategoryID                         FullCategoryName

0                                              Tools

2                                             Tools/Hammer

3                                              Tools/ScrewDriver

4                                              Tools/ScrewDriver/Phillips

5                                             Tools/ScrewDriver/Standard

6                                             Tools/ScrewDriver/Phillips/#2

7                                              Tools/ScrewDriver/Torx

8                                              Tools/ScrewDriver/Torx/#15

 

 

As I hope you are able to see is that I need the categoryID and the full path based off the parentID with a slash.

 

API Called Class

public class Categories_Web
{
    public string CategoryName { get; set; }
    public int CategoryParent { get; set; }
    public int CategoryID { get; set; }
}

Simplified Class with concatenated names

public class WebCategoriesSimple
{
    public int CategoryID { get; set; }
    public string CategoryName { get; set; }
}

I hope that this makes sense and thanks for your help!

4

2 Answers 2

1

What you have is a hierarchy, and whenever you have that you can consider Recursion - a method that calls itself. You don't always want to use recursion, but for manageable sized lists or tail call optimized recursive methods it is a powerful tool.

Here is demo code that outputs:

Category: 1, Hierarchy: Tools
Category: 2, Hierarchy: Tools/Hammer
Category: 3, Hierarchy: Tools/ScrewDriver
Category: 4, Hierarchy: Tools/ScrewDriver/Phillips
Category: 5, Hierarchy: Tools/ScrewDriver/Standard
Category: 6, Hierarchy: Tools/ScrewDriver/Phillips/#2
Category: 7, Hierarchy: Tools/ScrewDriver/Torx
Category: 8, Hierarchy: Tools/ScrewDriver/Torx/#15

(Note, I don't think your sample output of "0, Tools" is correct)

This program creates a hard-coded list of ProductDef. Yours probably comes from a database or something.

Then it creates an empty list of ProductHierarchy which will be populated as the recursive operation runs.

It then kicks off the recursion with the first call to Build(). Within that method, it will call itself while the item passed in has a parent in the hierarchy.

When there are no more parents, it adds the item to the list of ProductHierarchy.

void Main()
{
    List<ProductDef> pdefs = new List<UserQuery.ProductDef>{
        new ProductDef{Category = 1, Product = "Tools",  ParentCategory = 0},
        new ProductDef{Category = 2, Product = "Hammer",  ParentCategory = 1},
        new ProductDef{Category = 3, Product = "ScrewDriver",  ParentCategory = 1},
        new ProductDef{Category = 4, Product = "Phillips",  ParentCategory = 3},
        new ProductDef{Category = 5, Product = "Standard",  ParentCategory = 3},
        new ProductDef{Category = 6, Product = "#2",  ParentCategory = 4},
        new ProductDef{Category = 7, Product = "Torx",  ParentCategory = 3},
        new ProductDef{Category = 8, Product = "#15",  ParentCategory = 7}
    };

    //This will get filled as we go
    List<ProductHierarchy> phlist = new List<UserQuery.ProductHierarchy>();

    //kick off the recursion
    foreach (var element in pdefs)
    {
        Build(element, pdefs, element.Product, element.Category, phlist);
    }

    //do stuff with your list
    foreach (ProductHierarchy ph in phlist)
    {
        Console.WriteLine(ph.ToString());
    }
}

class ProductDef
{
    public int Category { get; set; }
    public string Product { get; set; }
    public int ParentCategory { get; set; }
}

class ProductHierarchy
{
    public int Category { get; set; }
    public string Hierarchy { get; set; }
    public override string ToString()
    {
        return $"Category: {Category}, Hierarchy: {Hierarchy}";
    }
}

void Build(ProductDef def, List<ProductDef> lst, string fullname, int cat, List<ProductHierarchy> phlist)
{
    string fullprodname = fullname;

    //obtain the parent category of product
    var parent = lst.FirstOrDefault(l => l.Category == def.ParentCategory);
    if (parent != null)
    {
        fullprodname = $"{parent.Product}/{fullprodname}";
        //recurse the call to see if the parent has any parents
        Build(parent, lst, fullprodname, cat, phlist);
    }
    else
    {
        //No further parents found, add it to a list
        phlist.Add( new ProductHierarchy { Category = cat, Hierarchy = fullprodname }); 
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

You are the BOMB!!! This is perfect... no need to change or edit a single item!!! Thank You so very much!
Crowcoder... This is perfect! Thanks for your help! It has been a rough last two days to get this. In SQL with a CTE it would have been easy but this is coming from a Web API return. I was writing code to stuff it in a temp table and query against it. Thanks again
You're welcome, it was an interesting challenge.
0

This is one of those problems that I think LINQ is great for. I'm having a bit of trouble understanding the format of your data. You can use a projection with LINQ, though, to project the results of the original list into a new list containing either a new object based on a class definition or a new anonymous class.

Projecting into a new anonymous class, using LINQ, would look something like the following. Keep in mind that we don't know your object names, because you didn't provide that information, so you may need to extrapolate a bit from my example.

(Note that I reread your post and I think I understand what your original objects may be called, so the post has been edited to use your class names.)

var newListOfStuff = originalListOfStuff.Select(s => new {
    CategoryID = s.CategoryID,
    CategoryName = $"Tools/{s.CategoryName}/#{s.CategoryParent}"
});

The string is created using string interpolation, which is new in C#. If your version of C# doesn't support that, you can use string.Format instead, like this:

string.Format("Tools/{0}/#{1}", s.CategoryName, s.CategoryParent);

With that new list, you can loop through it and use properties like this:

foreach(var item in newListOfStuff)
{
    Console.WriteLine(item.CategoryID);
    Console.WriteLine(item.CategoryName);
}

Here is a similar answer for references: https://stackoverflow.com/a/9885766/3096683

1 Comment

Thanks for the answer… this is close and I agree, from what little I know about LINQ it is the way to go!!! In SQL in a hierarchical relationship you can join the ChildID back to the ParentID and in this case the parent is the ParentCategoryID and the ChildID is the CategoryID. Then knowing the relationship continue the reference by concatenating the category names. This is to generate a type of breadcrumb trail... Does that help?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.