我尝试实现一种算法来生成带有分层标头的表。这些可以无限制嵌套。 渲染表标记的html示例如下:
<table border=1>
<thead>
<tr>
<th colspan="6">
Super one
</th>
<th colspan="6">
Super two
</th>
</tr>
<tr>
<th colspan="3">Head one</th>
<th colspan="3">Head two</th>
<th colspan="4">Head three</th>
<th colspan="2">Head four</th>
</tr>
<tr>
<th>Sub one</th>
<th>Sub two</th>
<th>Sub three</th>
<th>Sub four</th>
<th>Sub five</th>
<th>Sub six</th>
<th>Sub seven</th>
<th>Sub eight</th>
<th>Sub nine</th>
<th>Sub ten</th>
<th>Sub eleven</th>
<th>Sub twelve</th>
</tr>
</thead>
</table>
&#13;
表的配置应该以这种格式作为JavaScript对象传递:
var columns = [
{
label: 'Super one',
children: [
{
label: 'Head one',
children: [
{label: 'Sub one'},
{label: 'Sub two'},
{label: 'Sub three'}
]
},
{
label: 'Head two',
children: [
{label: 'Sub four'},
{label: 'Sub five'},
{label: 'Sub six'}
]
}
]
},
{
label: 'Super two',
children: [
{
label: 'Head three',
children: [
{label: 'Sub seven'},
{label: 'Sub eight'},
{label: 'Sub nine'},
{label: 'Sub ten'}
]
},
{
label: 'Head four',
children: [
{label: 'Sub eleven'},
{label: 'Sub twelve'}
]
}
]
}
];
现在,让我们忘记html渲染,只关注应该迭代配置的算法,以便有一个简单的2D数组格式:
var structure = [
[6, 6],
[3, 3, 4, 2],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];
其中每个条目表示包含其列定义(tr
)的表行(td
),数字表示colspan
。
我该如何实现算法?
目前我创建了一个递归函数,它根据配置返回总列数:
function getColumnCount(columns) {
var count = 0;
for (var i=0; i<columns.length; i++) {
var col = columns[i];
if (col.children && col.children.length > 0) {
count += getColumnCount(col.children);
}
else {
count++;
}
}
return count;
}
它按预期工作,但我一直试图生成&#34;结构&#34; array ...我当前(令人尴尬的)代码尝试是这样的:
function getStructure(columns) {
var structure = [[]];
for (var i=0; i<columns.length; i++) {
var col = columns[i];
if (col.children && col.children.length > 0) {
console.log(col.label, '(with children)');
schema[structure.length - 1].push(getColumnCount(col.children));
getStructure(col.children, schema);
}
else {
console.log(col.label, '(orphan)');
schema[structure.length - 1].push(1);
}
}
return structure;
}
我感觉真的很愚蠢,因为我知道它应该是一个相对容易的任务,但是当谈到递归函数时,我的大脑似乎拒绝合作XD
你能帮助我吗?
答案 0 :(得分:3)
棘手的部分是计算跨度,即给定节点下的叶节点数或1节点是叶本身。该值可以递归定义,如下所示:
numberOfLeaves(node) = if node.children then
sum(numberOfLeaves(child) for child in node.children)
else 1
其余的非常简单:
var columns = [
{
label: 'Super one',
children: [
{
label: 'Head one',
children: [
{
label: 'Sub one',
children: [
{label: 1},
{label: 2},
]
},
{label: 'Sub two'},
{label: 'Sub three'}
]
},
{
label: 'Head two',
children: [
{label: 'Sub four'},
{label: 'Sub five'},
{label: 'Sub six'}
]
}
]
},
{
label: 'Super two',
children: [
{
label: 'Head three',
children: [
{label: 'Sub seven'},
{label: 'Sub eight'},
{label: 'Sub nine'},
{label: 'Sub ten'}
]
},
{
label: 'Head four',
children: [
{label: 'Sub eleven'},
{label: 'Sub twelve'}
]
}
]
}
];
var tab = [];
function calc(nodes, level) {
tab[level] = tab[level] || [];
var total = 0;
nodes.forEach(node => {
var ccount = 0;
if ('children' in node) {
ccount = calc(node.children, level + 1);
} else {
ccount = 1;
}
tab[level].push({
label: node.label,
span: ccount
});
total += ccount;
});
return total;
}
calc(columns, 0);
console.log(tab);
function makeTable(tab) {
html = "<table border=1>";
tab.forEach(row => {
html += "<tr>";
row.forEach(cell => {
html += "<td colspan=" + cell.span + ">" + cell.label + "</td>"
});
html += "</tr>"
})
return html + "</table>";
}
document.write(makeTable(tab))
&#13;
答案 1 :(得分:2)
这是另一种更小的方法。
function getStructure(nodes) {
if (nodes.length == 0) { return [ [ 1 ] ] }
let level1 = nodes.map(node => getStructure(node.children ? node.children: []))
let ret = level1.reduce((obj, e) => e.map((a, i) => obj[i].concat(a)))
let sum = ret[0].reduce((sum, e) => sum + e, 0)
return [ [ sum ] ].concat(ret)
}
产生
[ [ 12 ],
[ 6, 6 ],
[ 3, 3, 4, 2 ],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ]
(我不确切知道如何处理“不同高度”的特征......结构应该如何?)
答案 2 :(得分:1)
这可以任意组合使用:
var columns = [
{
label: '1',
children: [
{
label: '1.1',
children: [
{label: '1.1.1'},
{
label: '1.1.2',
children: [
{label: '1.1.2.1'},
{label: '1.1.2.2'},
{label: '1.1.2.3'},
{label: '1.1.2.4'},
{label: '1.1.2.5'}
]
},
{label: '1.1.3'}
]
},
{
label: '1.2',
children: [
{label: '1.2.1'},
{label: '1.2.2'},
{label: '1.2.3'}
]
}
]
},
{
label: '2',
children: [
{
label: '2.1',
children: [
{label: '2.1.1'},
{label: '2.1.2'},
{label: '2.1.3'},
{
label: '2.1.4',
children: [
{label: '2.1.4.1'},
{label: '2.1.4.2'},
{
label: '2.1.4.3',
children: [
{label: '2.1.4.3.1'},
{label: '2.1.4.3.2'},
{
label: '2.1.4.3.3',
children: [
{label: '2.1.4.3.3.1'},
{label: '2.1.4.3.3.2'},
{label: '2.1.4.3.3.3'},
{label: '2.1.4.3.3.4'}
]
},
{label: '2.1.4.3.4'},
{label: '2.1.4.3.5'}
]
},
]
}
]
},
{
label: '2.2',
children: [
{label: '2.2.1'},
{
label: '2.2.2',
children: [
{label: '2.2.2.1'},
{label: '2.2.2.2'},
]
}
]
}
]
}
];
// table is the table
// cells is the array of cells we're currently processing
// rowIndex is the table row we're on
// colIndex is where the column for the current cell should start
function createTable(table, cells, rowIndex, colIndex)
{
// get the current row, add if its not there yet
var tr = table.rows[rowIndex] || table.insertRow();
// how many columns in this group
var colCount = cells.length;
// iterate through all the columns
for(var i = 0, numCells = cells.length; i < numCells; ++i)
{
// get the current cell
var currentCell = cells[i];
// we need to see where the last column for the current row is
// we have to iterate through all the existing cells and add their colSpan value
var columnEndIndex = 0;
for(var j = 0, numCellsInThisRow = tr.cells.length; j < numCellsInThisRow; ++j)
{
columnEndIndex += tr.cells[j].colSpan || 1;
}
// now we know the last column in the row
// we need to see where the column for this cell starts and add fillers in between
var fillerLength = colIndex - columnEndIndex;
while(fillerLength-- > 0)
{
tr.insertCell();
}
// now add the cell we want
var td = tr.insertCell();
// set the value
td.innerText = currentCell.label;
// if there are children
if(currentCell.children)
{
// before we go to the children row
// we need to see what the actual column for the current cell is because all the children cells will start here
// we have to iterate through all the existing cells and add their colSpan value
var columnEndIndex = 0;
// we don't need the current cell since thats where we want to start the cells in the next row
for(var j = 0, numCellsInThisRow = tr.cells.length - 1; j < numCellsInThisRow; ++j)
{
columnEndIndex += tr.cells[j].colSpan || 1;
}
// go to the next row and start making the cells
var childSpanCount = createTable(table, currentCell.children, rowIndex + 1, columnEndIndex);
// we want to add to this recursions total column count
colCount += childSpanCount - 1;
// set the colspan for this cell
td.colSpan = childSpanCount;
}
}
// return the total column count we have so far so it can be used in the previous recursion
return colCount;
}
function doIt()
{
var numCols = createTable(document.getElementById("output"), columns, 0, 0);
alert("total number of columns: " + numCols);
}
<html>
<body>
<a href="#" onclick="doIt(); return false">do it</a>
<br /><br />
<table id="output" border="1"></table>
</body>
</html>