php - 用PHP生成水平和垂直家谱表

标签 php recursion

我正在开发一种家谱类型的应用程序,允许将后代添加到可以尽可能深入和广泛的 parent 之下。我已经完美构建了数据库,这不是这里的问题。我遇到的问题是生成 HTML 格式的表格。

数据结构示例:

Array
(
    [1] => Array
        (
            [name] => Igor
            [children] => 2
            [siblings] => 0
            [level] => 1
            [descendants] => Array
                (
                    [7] => Array
                        (
                            [name] => Rapid
                            [children] => 2
                            [siblings] => 1
                            [level] => 2
                            [descendants] => Array
                                (
                                    [8] => Array
                                        (
                                            [name] => Hodor
                                            [children] => 1
                                            [siblings] => 1
                                            [level] => 3
                                            [descendants] => Array
                                                (
                                                    [9] => Array
                                                        (
                                                            [name] => Hodor II
                                                            [children] => 1
                                                            [siblings] => 0
                                                            [level] => 4
                                                            [descendants] => Array
                                                                (
                                                                    [20] => Array
                                                                        (
                                                                            [name] => Hodor III
                                                                            [children] => 0
                                                                            [siblings] => 0
                                                                            [level] => 5
                                                                        )

                                                                )

                                                        )

                                                )

                                        )

                                    [14] => Array
                                        (
                                            [name] => Rapid II
                                            [children] => 0
                                            [siblings] => 1
                                            [level] => 3
                                        )

                                )

                        )

                    [2] => Array
                        (
                            [name] => Thunder
                            [children] => 0
                            [siblings] => 1
                            [level] => 2
                        )

                )

        )

)

数字数组键是该人的 ID。

水平表所需的输出:

enter image description here

垂直:

enter image description here

我不确定在考虑行跨度和列跨度的同时递归循环数据的最佳方法是什么。我怎样才能在任何深度上有效地做到这一点?

最佳答案

我认为这段代码应该可以解决问题,它适用于您的示例数据和我尝试过的其他一些数据集。这是输出的屏幕截图:

Screenshot of the generated family trees.

有关所有部分如何工作的进一步说明请参见评论。

<?php

$tree = [
    [
        'name'        => 'Igor',
        'children'    => 2,
        'siblings'    => 0,
        'level'       => 1,
        'descendants' => [
            [
                'name'        => 'Rapid',
                'children'    => 2,
                'siblings'    => 1,
                'level'       => 2,
                'descendants' => [
                    [
                        'name'        => 'Hodor',
                        'children'    => 1,
                        'siblings'    => 1,
                        'level'       => 3,
                        'descendants' => [
                            [
                                'name'        => 'Hodor II',
                                'children'    => 1,
                                'siblings'    => 0,
                                'level'       => 4,
                                'descendants' => [
                                    [
                                        'name'     => 'Hodor III',
                                        'children' => 0,
                                        'siblings' => 0,
                                        'level'    => 5
                                    ]
                                ]
                            ]
                        ]
                    ],
                    [
                        'name'     => 'Rapid II',
                        'children' => 0,
                        'siblings' => 1,
                        'level'    => 3
                    ]
                ]
            ],
            [
                'name'     => 'Thunder',
                'children' => 0,
                'siblings' => 1,
                'level'    => 2
            ]
        ]
    ]
];

// Loop over the tree. Every person in the root of the tree
// gets his own table(s).
foreach ($tree as $person) {
    $rows = [];
    parsePerson($person, $rows);

    $rows = cleanupRows($rows);

    output($rows);
    $rows = convertRowsToHorizontal($rows);
    output($rows);
}

/**
 * Convert a person in the tree to an array to be used to print the tables.
 *
 * @param array $person
 * @param array $rows
 * @param int   $level
 * @param int   $position
 *
 * @return int
 */
function parsePerson($person, &$rows, $level = 0, $position = 0)
{
    if (!empty($person['descendants'])) {
        // The colspan of this row is the sum of the colspans of
        // its children
        $colspan = 0;

        foreach ($person['descendants'] as $descendant) {
            $colspan += parsePerson(
                $descendant,
                $rows,
                $level + 1,
                $position + $colspan
            );
        }
    } else {
        // If this person has no children, the colspan is 1.
        $colspan = 1;
    }

    $rows[$level][$position] = [
        'colspan' => $colspan,
        'name'    => $person['name']
    ];

    return $colspan;
}

/**
 * Insert empty cells where needed and sort by keys.
 *
 * @param array $rows
 *
 * @return array
 */
function cleanupRows($rows)
{
    $width = $rows[0][0]['colspan'];
    foreach ($rows as $rowNumber => $row) {
        $spanSoFar = 0;
        foreach ($row as $position => $cell) {
            // Insert empty cells in the row.
            if ($spanSoFar < $position) {
                for ($i = $spanSoFar; $i < $position; $i++) {
                    $rows[$rowNumber][$i] = ['name' => '', 'colspan' => 1];
                    $spanSoFar += 1;
                }
            }
            $spanSoFar += $cell['colspan'];
        }
        // Insert empty cells at the end of the row.
        if ($spanSoFar < $width) {
            for ($i = $spanSoFar; $i < $width; $i++) {
                $rows[$rowNumber][$i] = ['name' => '', 'colspan' => 1];
            }
        }
        // Sort cells by index.
        ksort($rows[$rowNumber]);
    }
    // Sort rows by index.
    ksort($rows);

    return $rows;
}

/**
 * Convert the table array from vertical representation to horizontal
 * representation.
 *
 * @param array $rows
 *
 * @return array
 */
function convertRowsToHorizontal($rows)
{
    // Create a new array containing all fields for the vertical representation
    // of the table.
    $newRows = [];

    // Fill the new array with data from the vertical table.
    foreach ($rows as $rowNumber => $row) {
        foreach ($row as $cellNumber => $cell) {
            $newRows[$cellNumber][$rowNumber] = [
                'name'    => $cell['name'],
                'rowspan' => $cell['colspan']
            ];
        }
    }

    ksort($newRows);

    return $newRows;
}

/**
 * Print the table.
 *
 * @param array $rows
 */
function output($rows)
{
    echo '<table border="1">';
    foreach ($rows as $row) {
        echo '<tr>';
        foreach ($row as $cell) {
            if (!empty($cell['colspan'])) {
                echo '<td colspan="' . $cell['colspan'] . '" align="center">';
            } else {
                echo '<td rowspan="' . $cell['rowspan'] . '" align="center">';
            }
            echo $cell['name'];
            echo '</td>';
        }
        echo '</tr>';
    }
    echo '</table>';
}

正如您所看到的,示例数组中的大部分数据(子级、兄弟级和级别)都没有使用,因此您不妨简化数组结构:

<?php

$tree = [
    'Igor' => [
        'Rapid'   => [
            'Hodor'    => [
                'Hodor II' => [
                    'Hodor III' => null
                ]
            ],
            'Rapid II' => null
        ],
        'Thunder' => [
            'Thunder II' => [
                'Thunder III' => [
                    'Thunder IV' => [
                        'Thunder V' => null
                    ]
                ]
            ]
        ]
    ]
];

// Loop over the tree. Every person in the root of the tree
// gets his own table(s).
foreach ($tree as $name => $children) {
    $table = [];
    parsePerson($name, $children, $table);
    $table = cleanupRows($table);

    output($table);
    $table = convertRowsToHorizontal($table);
    output($table, true);
}

/**
 * Convert a person in the tree to an array to be used to print the tables.
 * The span of a person is either the sum of its children's spans,
 * or 1 if it has no children.
 *
 * @param string $name
 * @param array  $children
 * @param array  $table
 * @param int    $level
 * @param int    $position
 *
 * @return int
 */
function parsePerson($name, $children, &$table, $level = 0, $position = 0)
{
    if (!empty($children)) {
        $span = 0;

        foreach ($children as $childName => $childChildren) {
            $span += parsePerson(
                $childName,
                $childChildren,
                $table,
                $level + 1,
                $position + $span
            );
        }
    } else {
        $span = 1;
    }

    $table[$level][$position] = getCell($name, $span);;

    return $span;
}

/**
 * Insert empty cells where needed and sort by keys.
 *
 * @param array $table
 *
 * @return array
 */
function cleanupRows($table)
{
    $width = $table[0][0]['span'];

    foreach ($table as $rowNumber => $row) {
        $spanSoFar = 0;
        foreach ($row as $position => $cell) {
            addExtraCells($table, $spanSoFar, $rowNumber, $position);
            $spanSoFar += $cell['span'];
        }
        addExtraCells($table, $spanSoFar, $rowNumber, $width);
        ksort($table[$rowNumber]);
    }
    ksort($table);

    return $table;
}

/**
 * @param array $table
 * @param int   $spanSoFar
 * @param int   $rowNumber
 * @param int   $position
 */
function addExtraCells(&$table, &$spanSoFar, $rowNumber, $position)
{
    while ($spanSoFar < $position) {
        $table[$rowNumber][$spanSoFar] = getCell();
        $spanSoFar += 1;
    }
}

/**
 * @param string $name
 * @param int    $span
 *
 * @return array
 */
function getCell($name = '', $span = 1)
{
    return ['name' => $name, 'span' => $span];
}

/**
 * Convert the table array from vertical representation to horizontal
 * representation. By switching 1st and 2nd level array keys.
 *
 * @param array $table
 *
 * @return array
 */
function convertRowsToHorizontal($table)
{
    $horizontal = [];

    foreach ($table as $rowNumber => $row) {
        foreach ($row as $cellNumber => $cell) {
            $horizontal[$cellNumber][$rowNumber] = $cell;
        }
    }
    ksort($horizontal);

    return $horizontal;
}

/**
 * Print the table.
 *
 * @param array $table
 * @param bool  $horizontal
 */
function output($table, $horizontal = false)
{
    $colRow = $horizontal ? 'row' : 'col';

    echo '<table border="1">';
    foreach ($table as $row) {
        echo '<tr>';
        foreach ($row as $cell) {
            echo '<td ' . $colRow . 'span="' . $cell['span'];
            echo '" align="center">';
            echo $cell['name'];
            echo '</td>';
        }
        echo '</tr>';
    }
    echo '</table>';
}

关于php - 用PHP生成水平和垂直家谱表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37653952/

相关文章:

php - 从链接中提取域

c - C 中通过管道重定向 stdin 和 stdout 适用于外部程序,但不适用于递归调用

algorithm - 交叉点 : Strassen's Algorithm

javascript - 更新 Canvas 元素而不刷新页面

php - 为每个系列创建一个下拉菜单

php - 尝试在 Preg_Match 中查找正斜杠

haskell - gpostpro "escape from the monad"怎么办?

php - 使用ajax在php中显示成功消息

javascript - 对 JavaScript 中的递归和执行流程有更清晰的解释吗?

recursion - 将自身称为 goroutine 的 golang 递归函数无法按预期工作