0

I have a nested list structure as follows:

class Person {
   public string Name;
   public List<Person> Children;
   public int RowSpan { 
       get 
       { int childrenCount = 0;
          /* go through children (if any) and update childrenCount */ 
          return childrenCount == 0 ? 1 : childrenCount;
       }
    };
}

EDIT: my real data structure has nothing to do with parents/ children. I just choose a simple data structure to make the question easy to understand. Assume each grandpa can have any number of sons. Each son can have any number of kids. No two grandpas can share the same son. Likewise, sons can't share kids.

Edit2: I don't have issues with getting RowSpan for each cell. That is easy. The issue is: how can I parse the HTML structure of the table while iterating and traversing the data structure.

I populate a sample of this structure as follows:

Person grandpa1  = new Person { Name = "GrandPa1" };
Person grandpa2  = new Person { Name = "GrandPa2" };
Person son1      = new Person { Name = "Son 1" };
Person son2      = new Person { Name = "Son 2" };
Person grandkid1 = new Person { Name = "GrandKid1" };
Person grandkid2 = new Person { Name = "GrandKid2" };
Person grandkid3 = new Person { Name = "GrandKid3" };

grandpa1.Children = new List<Person> { son1, son2 };
son2.Children    = new List<Person> { grandkid1, grandkid2, grandkid3 };

List<Person> family = new List<Person> { grandpa1, grandpa2 } ;

I am trying to represent the family structure in a HTML table, using <td rowspan={number of offsprints}>

For the above family, the output would look like:

.------------------.
|          |       |
|          |  son1 |
|          |-------|-----------.
|          |       | grandkid1 |
| grandpa1 |       |-----------.
|          |       | grandkid2 |
|          |  son2 |-----------.
|          |       | grandkid3 |
|----------|-------.-----------.
| grandpa2 |
.----------.

In the above table, grandpa has a rowspan of 4. son2 has a rowspan of 3.

The equivelant HTML would be something like:

table {
  border-collapse: collapse;
}

td, th {
  border: 1px solid black;
  padding: 4px;
}

th
{
  font-family: monospace;
  background-color: #def;
  padding: 0 2em;
}
<table>
  <thead>
    <tr>
      <th>Grand Parents</th>
      <th>Sons</th>
      <th>Kids</th>
    </tr>
  </thead>
  <tr>
    <td rowspan="4"> grandpa1 </td>
    <td> son1 </td>
  </tr>
  <tr>
    <!-- no cell for grandpa1:in progress -->
    <!-- no cell for son1: fulfilled      -->
    <td rowspan="3"> son2 </td>
    <td> kid1 </td>
  </tr>
  <tr>
    <!-- no cell for grandpa1: in progress -->
    <!-- no cell for son2:     in progress -->
    <td> kid2 </td>
  </tr>
  <tr>
    <!-- no cell for grandpa1: in progress -->
    <!-- no cell for son2:     in progress -->
    <td> kid3</td>
  </tr>
  <tr>
    <!-- no cell for grandpa1: fullfilled -->
    <!-- no cell for son2:     fullfilled -->
    <td> grandpa2 </td>
  </tr>
</table>

I am struggling to write an algorithm that generates the HTML structure described above. I tried a number of ways:

1- I generated a Queue<TagBuilder> for each level of the family and iterate through the highest level and Enqueue a TD with a rowspan of the number of dependants in the lower levels. Then I generated other queues for the other levels. At the end, my plan was to DeQueue from all levels at once and append the TDs into a fresh TR. But that doesn't work because son1 has no children, and dequeuing from level 3 would fetch kids of son2..

2- I tried to iterate though grandparents level and appending a TR for each grandparent with a single TD having the rowspan of his sons/grandsons. Then iterate through sons level and do the same, likewise in grandkids level. However the overall table will be ill-formatted.

I can easily generate the structure in Excel since I have the ability to reference cells and their merge status while iterating the first level, and can comeback to the rows from before and append new cells.

Is there an easy way to generate the family structure table from the data structure above?

4
  • How would you represent the children of two parents? It generally takes two to make one. I'd expect grandpa1 and grandpa2 to have the same children, although that is not always the case (you can cover step-parenthood as well) Commented Jul 11, 2021 at 12:00
  • @Andrei15193 I updated my question. No sharing of offsprings in all levels. Commented Jul 11, 2021 at 12:14
  • Try adding a RowSpan property to the person class, and update that as you walk through each of the items. This seems like a good use case for writing a recursive function to walk each of the relationships. Commented Jul 11, 2021 at 12:51
  • @Ahmad In this case, if you look at it as a tree, what you need for each node is the number of leaves it has. I.e.: the grandparent has 2 children, 1 has 2 children and the other 3. The Leaves from the grandparent node is 5 (5 grandchildren in total) while for the first child it's 2 and the other 3. For each grandchild is 1 because each of them is a leaf in the tree. You can write a method that gets you the number of leaves from the current node, including the current node. Commented Jul 11, 2021 at 13:02

1 Answer 1

1

My previous answer does not produce the desired result. See the below answer.

Actually, I think it would be easier to "tablefy" the tree into a 2-dimensional array and fill it with the respective values. To determine the row span you need to look in the area below it for null values. The row span will be equal to the number of rows that contain null on all columns up to, and including, the current column.

For instance, Son1 does not have any null directly bellow it meaning its row span is 1. Son2 has 4 null values, but the row span is 3 because on row index 4 and column index 0 there is Grandpa 2. This is why looking directly below the current row (same column) is not enough, you need to check the area below the current cell on each column from start to, and including, current column.

You should be able to expand this to any number of levels and still get a proper table.

class Person
{
    public string Name { get; set; }

    public List<Person> Children { get; set; }
}

void WritePeopleTable(TextWriter textWriter, IEnumerable<Person> people)
{
    var peopleTable = GetPeopleTable(people);

    textWriter.WriteLine("<table>");
    for (var rowIndex = 0; rowIndex < peopleTable.GetLength(0); rowIndex++)
    {
        textWriter.WriteLine("<tr>");
        for (var columnIndex = 0; columnIndex < peopleTable.GetLength(1); columnIndex++)
        {
            var current = peopleTable[rowIndex, columnIndex];
            if (current != null)
            {
                var rowSpan = 1;
                var foundValue = false;
                while (!foundValue && rowIndex + rowSpan < peopleTable.GetLength(0))
                {
                    var searchColumnIndex = 0;
                    while (!foundValue && searchColumnIndex <= columnIndex)
                        if (peopleTable[rowIndex + rowSpan, searchColumnIndex] != null)
                            foundValue = true;
                        else
                            searchColumnIndex++;

                    if (!foundValue)
                        rowSpan++;
                }

                textWriter.Write($"<td rowspan=\"{rowSpan}\">");
                textWriter.Write(current.Name);
                textWriter.WriteLine("</td>");
            }
        }
        textWriter.WriteLine("</tr>");
    }
    textWriter.WriteLine("</table>");
}

struct PersonLevel
{
    public PersonLevel(Person person, int level)
    {
        Person = person;
        Level = level;
    }

    public Person Person { get; }

    public int Level { get; }
}

Person[,] GetPeopleTable(IEnumerable<Person> people)
{
    var rowCount = people.Sum(person => GetLeafCount(person)); // or people.Sum(GetLeafCount);
    var columnCount = people.Max(person => GetTreeHeight(person)); // or people.Max(GetTreeHeight);
    var peopleTable = new Person[rowCount, columnCount];
    var currentRowIndex = 0;
    var previousColumnIndex = -1;
    var toProcess = new Stack<PersonLevel>();
    foreach (var person in people.Reverse())
        toProcess.Push(new PersonLevel(person, 0));

    while (toProcess.Count > 0)
    {
        var current = toProcess.Pop();
        if (current.Person.Children != null)
            foreach (var child in current.Person.Children.AsEnumerable().Reverse())
                toProcess.Push(new PersonLevel(child, current.Level + 1));

        if (current.Level <= previousColumnIndex)
            currentRowIndex++;
        previousColumnIndex = current.Level;

        peopleTable[currentRowIndex, current.Level] = current.Person;
    }

    return peopleTable;
}

int GetLeafCount(Person person)
{
    var leafCount = 0;
    var toVisit = new Queue<Person>();
    toVisit.Enqueue(person);
    do
    {
        var current = toVisit.Dequeue();
        if (current.Children == null || !current.Children.Any())
            leafCount++;
        else
            foreach (var child in current.Children)
                toVisit.Enqueue(child);
    } while (toVisit.Count > 0);

    return leafCount;
}

int GetTreeHeight(Person person)
{
    var height = 0;
    var level = Enumerable.Repeat(person, 1);
    do
    {
        height++;
        level = level.SelectMany(current => current.Children ?? Enumerable.Empty<Person>());
    } while (level.Any());

    return height;
}

Previous answer

As I said in the comments, the row span is equal to the number of leaves from the current node (including the current node, row span of 1 is default).

int GetLeafCount(Person person)
{
    var leafCount = 0;
    var toVisit = new Queue<Person>();
    toVisit.Enqueue(person);
    do
    {
        var current = toVisit.Dequeue();
        if (!current.Children.Any())
            leafCount++;
        else
            foreach (var child in current.Children)
                toVisit.Enqueue(child);

    } while (toVisit.Count > 0);

    return leafCount;
}

Something you need to consider when doing this is the scenario when a child provides no grandchildren for the parent, in that case you need to specify a col span as well to fill the space, or fill it with the remaining TD elements. You can get the number of columns by calculating the height of the tree.

int GetTreeHeight(Person person)
{
    var height = 0;
    var level = Enumerable.Repeat(person, 1);
    do
    {
        height++;
        level = level.SelectMany(current => current.Children);
    } while (level.Any());

    return height;
}

You may need to get a collection of people that are on the same level in regards to the parent, i.e.: all people that have the same parent, all people that have the same grand parent and so on.

IEnumerable<IEnumerable<Person>> GetLevels(Person person)
{
    var level = Enumerable.Repeat(person, 1);
    do
    {
        yield return level;
        level = level.SelectMany(current => current.Children);
    } while (level.Any());
}

You can use these methods to generate the table.

void WriteTable(TextWriter textWriter, IEnumerable<Person> people)
{
    textWriter.WriteLine("<table>");
    foreach (var person in people)
        foreach (var rowIndex in Enumerable.Range(0, GetLeafCount(person)))
        {
            textWriter.WriteLine("<tr>");
            foreach (var columnIndex in Enumerable.Range(0, GetTreeHeight(person)))
            {
                var currentPerson = GetLevels(person).ElementAtOrDefault(columnIndex)?.ElementAtOrDefault(rowIndex);

                if (currentPerson != null)
                {
                    textWriter.Write($"<td rowspan=\"{GetLeafCount(currentPerson)}\">");
                    textWriter.Write(currentPerson.Name);
                    textWriter.WriteLine("</td>");
                }
            }
            textWriter.WriteLine("</tr>");
        }
    textWriter.WriteLine("</table>");
}
Sign up to request clarification or add additional context in comments.

6 Comments

I don't have any issues getting the numberOfchildren at any node. What I am struggling with is the layout of the table as it is parsed to the browser. I can't just have as many TR as there are grandparents.
@Ahmad you need as many TR as there are leaves at the root nodes (each grand parent). Total number of leaves, basically.
I am not sure I would need a TR for each root node. Please see my update question again
@Ahmad not each root node, each root node has a number of leaves, you sum these up and get the total number of TRs, I've updated my answer.
I managed to modify your code so that it worked. I just needed a push towards the right direction.
|

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.