简体   繁体   English

数组中嵌套对象的JavaScript递归函数

[英]JavaScript recursive function for nested objects in array

I'm trying to implement an algorithm to generate a table with hierarchical headers. 我正在尝试实现一个算法来生成一个带有分层标头的表。 These ones can be unlimited nested. 这些可以无限制嵌套。 An html example of the rendered table markup could be the following: 呈现的表标记的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> 

The configuration of the table should be passed as a JavaScript object in this format: 表的配置应该以这种格式作为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'}
        ]
      }
    ]
  }
];

Now, let's forget about the html rendering and pay attention only to the algorithm that should iterate over the configuration in order to have a simple 2D array in the format: 现在,让我们忘记html渲染,并且只关注应该迭代配置的算法,以便以下面的格式获得一个简单的2D数组:

var structure = [
  [6, 6],
  [3, 3, 4, 2],
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]; 

where each entry represents a table row ( tr ) containing its column definition ( td ) and the number represents the colspan . 其中每个条目表示包含其列定义( td )的表行( tr ),数字表示colspan How can I implement the algorithm? 我该如何实现算法?

Currently I created a recursive function which returns the number of total columns based on the configuration: 目前我创建了一个递归函数,它根据配置返回总列数:

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

it works as expected, but I'm stuck trying to generate the "structure" array... my current (embarrassing) code attempt is this: 它按预期工作,但我试图生成“结构”数组...我当前(令人尴尬)的代码尝试是这样的:

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

I'm feeling a real dumb, since I know it should be a relatively easy task, but when it comes to recursive functions my brain seems to refuse to collaborate XD 我感觉真的很愚蠢,因为我知道它应该是一个相对容易的任务,但是当涉及到递归函数时,我的大脑似乎拒绝合作XD

Can you help me? 你能帮助我吗?

The tricky part is to calculate a span, which is the number of leaf nodes under the given node or 1 the node is a leaf itself. 棘手的部分是计算跨度,即给定节点下的叶节点数或1节点是叶本身。 This value can be defined recursively as follows: 该值可以递归定义,如下所示:

 numberOfLeaves(node) = if node.children then 
       sum(numberOfLeaves(child) for child in node.children) 
       else 1

The rest is pretty straightforward: 其余的非常简单:

 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)) 

Here another and a bit smaller approach. 这里是另一种更小的方法。

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

produces 产生

[ [ 12 ],
  [ 6, 6 ],
  [ 3, 3, 4, 2 ],
  [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ]

(I don't know exactly how to deal with the "different height" feature... how should the structure look like?) (我不确切知道如何处理“不同高度”的特征......结构应该怎么样?)

This should work in any combination: 这应该以任何组合方式工作:

 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> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM