Search code examples
recursionparent-childhierarchical-datasubtotal

Hierarchical Summation of Parent Child in C#


I have the following model

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

    public bool IsGroup { get; set; }

    public int? ParentId { get; set; }

    public List<YearCost> YearCost { get; set; } = new List<YearCost>();
}

class YearCost
{
    public int Year { get; set; }
    public decimal Cost { get; set; }
}

i have this sample list populated using the models above

    static void Main(string[] args)
    {

        var entries = new List<Entry> {
             new Entry
             {
                 Id = 1,
                 ParentId = null, 
                 IsGroup = true,
             },
             new Entry
             {
                 Id = 2,
                 ParentId = 1,
                 IsGroup = false,
                 YearCost = new List<YearCost> {
                 new YearCost { Year = 2019, Cost = 10 },
                 new YearCost { Year = 2020, Cost = 10 }
                 }
             },
             new Entry
             {
                 Id = 3,
                 ParentId = 1,
                 IsGroup = true
             },
             new Entry
             {
                 Id = 4,
                 ParentId = 3,
                 IsGroup = true
             },
             new Entry
             {
                 Id = 5,
                 ParentId = 4,
                 IsGroup = false,
                 YearCost = new List<YearCost> {
                 new YearCost { Year = 2019, Cost = 15 },
                 new YearCost { Year = 2020, Cost = 10 }
                 }
             },
             new Entry
             {
                 Id = 6,
                 ParentId = 4,
                 IsGroup = false,
                 YearCost = new List<YearCost> {
                 new YearCost { Year = 2019, Cost = 15 },
                 new YearCost { Year = 2020, Cost = 10 }
                 }
             },
             new Entry
             {
                 Id = 7,
                 ParentId = 3,
                 IsGroup = true
             },
             new Entry
             {
                 Id = 8,
                 ParentId = 7,
                 IsGroup = false,
                 YearCost = new List<YearCost> {
                 new YearCost { Year = 2019, Cost = 30 },
                 new YearCost { Year = 2020, Cost = 30 }
                 }
             },
             new Entry
             {
                 Id = 9,
                 ParentId = 7,
                 IsGroup = false,
                 YearCost = new List<YearCost> {
                 new YearCost { Year = 2019, Cost = 20 },
                 new YearCost { Year = 2020, Cost = 20 }
                 }
             },
             new Entry
             {
                 Id = 10,
                 ParentId = 3,
                 IsGroup = false,
                 YearCost = new List<YearCost> {
                 new YearCost { Year = 2019, Cost = 5 },
                 new YearCost { Year = 2020, Cost = 5 }
                 }
             },
        }; 

        Console.WriteLine(String.Format("{0,10}{1,10}{2,10}{3, 10}{4, 10}", "Id", "Group", "Parent Id", 2019, 2020));
        Console.WriteLine(String.Format("{0,10}{1,10}{2,10}{3, 10}{4, 10}", "--", "-----", "---------", "----", "----"));
        foreach (var entry in entries.OrderBy(x=>x.ParentId))
        {
            Console.Write(String.Format("{0,10}{1,10}{2,10}", entry.Id, entry.IsGroup ? "yes" : "no", entry.ParentId?.ToString() ?? "NULL", 2019, 2020));
            foreach (var y in entry.YearCost)
                Console.Write(String.Format("{0,10}", y.Cost));
            Console.WriteLine("\n");
        }
    }

Rule #1: only entry which is not a group has cost values entered manually by user while the group entry cost is calculated Rule #2: nesting of groups are allowed.

what i want is to do hierarchical summation for each group as shown in the table below the value inside the square brackets has to be calculated.

    Id     Group    Parent Id    2019      2020
    --     -----    ---------    ----      ----
     1       yes       NULL      [95]      [85]
     2       no          1        10        10 
     3       yes         1       [85]      [75]
     4       yes         3       [30]      [20]
     7       yes         3       [50]      [50]
    10       no          3         5        5
     5       no          4        15        10
     6       no          4        15        10
     8       no          7        30        30
     9       no          7        20        20

Thanks in Advance


Solution

  • I've managed finally to have the answer

    first you need to group element by parent

      var groups = entries.ToLookup(x => x.ParentId).ToDictionary(x => x.Key ?? 0, x 
    
      => x.ToArray().Select(e => e.Id).ToList());
    

    then get all children helpers

    private List<int> GetAllChildren(int? parent, Dictionary<int, List<int>> groups)
        { 
            List<int> children = new List<int>();
            PopulateChildren(parent, children, groups);
            return children;
        }
    
        private void PopulateChildren(int? parent, List<int> children, Dictionary<int, List<int>> groups)
        {
            List<int> myChildren;
            if (groups.TryGetValue(parent.Value, out myChildren))
            {
                children.AddRange(myChildren);
                foreach (int child in myChildren)
                {
                    PopulateChildren(child, children, groups);
                }
            }
        }
    

    Then Iterate over the list to populate the totals

    foreach (var item in entries)
    {
        if (item.IsGroup)
        {
            var children = GetAllChildren(item.Id, groups);
            children.ForEach(c => {
                var entry = entries.FirstOrDefault(x => x.Id == c);
                if(entry != null)
                {
                    if (!item.isGroup)
                    {
                        entry.YearCosts?.ForEach(y =>
                        {
                                if (item.YearCosts.FirstOrDefault(yx => yx.Year == y.Year) == null)
                                {
                                    item.YearCosts.Add(new YearCost { Year = y.Year, Cost = 0 });
                                }
                                item.YearCosts.FirstOrDefault(yx => yx.Year == y.Year).Cost += y.Cost ?? 0;
                                item.SubTotal += y.Cost ?? 0;
                        });
                    }
                } 
            }); 
        }
    }