0

我有一个嵌套列表结构如下:

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

编辑:我的真实数据结构与父母/孩子无关。我只是选择了一个简单的数据结构,使问题易于理解。假设每个爷爷可以有任意数量的儿子。每个儿子可以有任意数量的孩子。没有两个爷爷可以共享同一个儿子。同样,儿子不能分享孩子。

Edit2:我没有为每个单元格获取 RowSpan 的问题。这很容易。问题是:如何在迭代和遍历数据结构时解析表格的 HTML 结构。

我填充此结构的示例如下:

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

我试图family在 HTML 表中表示结构,使用<td rowspan={number of offsprints}>

对于上述系列,输出如下所示:

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

在上表中,有grandpa一个rowspan。有一个。4son2rowspan3

等效的 HTML 将类似于:

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>

我正在努力编写一种生成上述 HTML 结构的算法。我尝试了多种方法:

1-我Queue<TagBuilder>为家庭的每个级别生成一个并遍历最高级别和Enqueue一个TD具有较低级别的受抚养人数量的行跨度。然后我为其他级​​别生成了其他队列。最后,我的计划是DeQueue一次从所有级别添加 TD 到一个新的TR. 但这不起作用,因为son1没有孩子,从第 3 级出列会获取son2..

2-我尝试迭代祖父母级别并为每个祖父母附加一个TR,其中一个TD具有他的儿子/孙子的行跨度。然后遍历儿子级别并做同样的事情,同样在孙子级别。但是,整个表格的格式会不正确。

我可以在 Excel 中轻松生成结构,因为我能够在迭代第一级时引用单元格及其合并状态,并且可以从之前的行返回并附加新单元格。

有没有一种简单的方法可以从上面的数据结构中生成家庭结构表?

4

1 回答 1

1

我之前的回答没有产生预期的结果。请参阅下面的答案。

实际上,我认为将树“表格化”成二维数组并用相应的值填充它会更容易。要确定行跨度,您需要在其下方的区域中查找null值。行跨度将等于所有列上包含 null 的行数,包括当前列。

例如,Son1 没有任何null直接在它下面,这意味着它的行跨度是 1。Son2 有 4 个null值,但行跨度是 3,因为在行索引 4 和列索引 0 上有爷爷 2。这就是为什么直接看下面的原因当前行(同一列)是不够的,您需要检查从开始到当前列(包括当前列)每列上当前单元格下方的区域。

您应该能够将其扩展到任意数量的级别,并且仍然可以获得正确的表格。

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

上一个答案

正如我在评论中所说,行跨度等于当前节点的叶子数(包括当前节点,默认行跨度为 1)。

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

执行此操作时需要考虑的情况是子代没有为父代提供孙代,在这种情况下,您还需要指定一个 col 跨度来填充空间,或者用剩余的 TD 元素填充它。您可以通过计算树的高度来获得列数。

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

您可能需要收集在父母方面处于同一级别的人,即:所有具有相同父母的人,所有具有相同祖父母的人等等。

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

您可以使用这些方法来生成表。

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>");
}
于 2021-07-11T13:06:36.500 回答