将DAG渲染成表格的算法

4

我有一个DAG,代表用堆栈数据流语言编写的程序。每个节点表示一个函数或值。边缘表示输入和输出,其中每个节点可能有0个或多个。

首先,这是代码:

def to_table(nodes):
    table = []
    table_width = 0
    empty = (None, 1)

    for name, indegree, outdegree in nodes:
        cell_width = max(indegree, outdegree)

        # Create a row of empty cells, the width of the table.
        current_row = [empty] * table_width

        # Right-pad preceding rows.
        for row in table:
            row.extend([empty] * cell_width)
        table_width += cell_width

        for n in range(indegree):

            # If we've run out of inputs, left-pad preceding rows.
            if len(current_row) == 0:
                current_row.append(empty)
                for row in table:
                    row = [empty] + row
                table_width += 1

            # Drop one empty cell.
            current_row.pop()
            for row in table:
                row.pop();
            table_width -= 1

        current_row.append((name, cell_width))
        table.append(current_row)

    return table

算法接受这样的图形:
1   2   3   4
 \ /     \ /
  +       +
   \     /
    \   /
     \ /
      *
      |
    print

以后序遍历的方式表示,其中每个元素都是一个元组(名称,入度,出度):

[
  ("1", 0, 1),
  ("2", 0, 1),
  ("+", 2, 1),
  ("3", 0, 1),
  ("4", 0, 1),
  ("+", 2, 1),
  ("*", 2, 1),
  ("print", 1, 0),
]

将其渲染为表格。
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
|   +   |   +   |
+-------+-------+
|       *       |
+---------------+
|     print     |
+---------------+

即,独立节点水平排列,依赖关系通过垂直排列表示。如果一个函数没有足够的输入,则会产生空单元格。
o   +---+
    | 2 |
+---+---+
|   *   |
+-------+

该算法通过为每个单元格添加一个新的空行,并在右侧填充表格,删除右侧的单元格或在左侧添加它们以“下沉”节点,直到其输入满足为止。

+---+
| 1 |
+---+

+---+   o
| 1 |
+---+---+
    | 2 |
o   +---+

+---+           o
| 1 |
+---+---+
    | 2 |
    +---+-------+
        |   +   |
o       +-------+

+---+       o
| 1 |
+---+---+
    | 2 |
    +---+---+
    |   +   |
o   +-------+

+---+   o
| 1 |
+---+---+
    | 2 |
+---+---+
|   +   |
+-------+

最后一步(在上面的代码中被省略了)是将占用的单元格与空单元格垂直合并。
+---+---+
| 1 | 2 |
+---+---+
|   +   |
+-------+

问题在于这种方法没有考虑到在将新单元格插入行时列宽的变化。
+---+---+---+       o
| 1 | 2 | 3 |
+---+---+---+
    |   +   |
    +-------+-------+
            |   +   |
o           +-------+

+---+---+---+   o
| 1 | 2 | 3 |
+---+---+---+
    |   +   |
    +---+---+---+
        |   +   |
o       +-------+


Wrong output:
+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
    |   +   |
    +-------+
    |   +   |
o   +-------+

Desired output:
+---+---+---+
|   | 2 | 3 |
| 1 +---+---+
|   |   +   |
+---+-------+
|     +     |
+-----------+

我该如何在算法中跟踪这些信息?使用前一行的列宽列表吗?此外,有没有更有效的方法来处理这个问题?当前算法对每个节点的每个输入都要遍历整个表。

最后,这是测试用例:

def render(table):
    print "<table>"
    for row in table:
        print "<tr>"
        for name, width in row:
            if name is None:
                print "<td class='empty' colspan='%d'>&nbsp;</td>" % width
            else:
                print "<td colspan='%d'>%s</td>" % (width, name)
        print "</tr>"
    print "</table>"

print """<!DOCTYPE html>
<html><head><title>Test</title>
<style>
td { border: 1px solid black }
td.empty { border: 1px dashed grey }
</style>
</head><body>"""
render(to_table([
    ("1", 0, 1),
    ("2", 0, 1),
    ("3", 0, 1),
    ("+", 2, 1),
    ("+", 2, 1),
]))
print """</body></html>"""
1个回答

2
在生成的表格中,每一个图节点都有一个单元格,这意味着可以通过充分装饰图来描述表格。我们需要的主要附加属性是每个节点的rowspan和colspan。
最简单的属性是colspan,它等于子节点的colspan之和,如果没有子节点则为1。
而rowspan可以通过gheight(parent) - gheight(node)得到,其中gheight(node)是其子节点的gheight的最大值加1,如果没有子节点则为0。
在一次遍历输入数据时,您可以计算出节点的'colspan'和'gheight'属性。在您的例子中,它们是[(1, 0), (1, 0), (1, 0), (2, 1), (3, 2)]。
通过很少的额外工作,您可以在同一个循环中计算出rowspan。以下是我的实现。
NAME, INDEGREE, OUTDEGREE, ROWSPAN, COLSPAN, GHEIGHT = range(6)

def decorate_graph(nodes):
    deco = []
    stk = []
    for name, indegree, outdegree in nodes:
        node = [name, indegree, outdegree, 0, 1, 0]
        deco.append(node)
        if indegree:
            subn = stk[-indegree:]
            del stk[-indegree:]
            node[COLSPAN] = sum(sn[COLSPAN] for sn in subn)
            node[GHEIGHT] = 1 + max(sn[GHEIGHT] for sn in subn)
            for sn in subn:
                sn[ROWSPAN] = node[GHEIGHT] - sn[GHEIGHT]
        stk.append(node)
    g = 1 + max(n[GHEIGHT] for n in stk)
    for n in stk:
        n[ROWSPAN] = g - n[GHEIGHT]
    return deco

if __name__ == '__main__':
    g = [
        ("1", 0, 1),
        ("2", 0, 1),
        ("3", 0, 1),
        ("+", 2, 1),
        ("+", 2, 1),
    ]
    d = decorate_graph(g)
    for item in d:
        print(item)

输出结果为:
['1', 0, 1, 2, 1, 0]
['2', 0, 1, 1, 1, 0]
['3', 0, 1, 1, 1, 0]
['+', 2, 1, 1, 2, 1]
['+', 2, 1, 1, 3, 2]

每个节点都被装饰了rowspan、colspan和gheight属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接