【问题标题】:JavaScript recursive function for nested objects in array数组中嵌套对象的JavaScript递归函数
【发布时间】:2016-05-26 15:14:42
【问题描述】:

我正在尝试实现一种算法来生成带有分层标题的表。这些可以无限嵌套。 呈现的表格标记的 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>  

表格的配置应该以这种格式作为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 渲染,只关注应该迭代配置的算法,以便获得一个简单的二维数组格式:

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

其中每个条目表示包含其列定义 (td) 的表行 (tr),数字表示 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;
}

它按预期工作,但我一直在尝试生成“结构”数组...我当前(令人尴尬的)代码尝试是这样的:

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

你能帮帮我吗?

【问题讨论】:

  • 配置中的所有兄弟都有深度?
  • 我猜你的意思是如果他们有 same 深度......在这种情况下,好问题......我应该能够处理不同的深度......例如“头四”可以不配置“子十一”和“子十二”
  • 你能给我举个例子说明structure应该是什么样子吗?

标签: javascript arrays algorithm recursion


【解决方案1】:

棘手的部分是计算跨度,它是给定节点下的叶子节点的数量或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))

【讨论】:

  • 如果你在下面添加更多的孩子而不在上面添加它们,这会起作用吗?说你在“十一”下添加了孩子?我认为它不会添加必要的填充物......
  • @IMTheNachoMan:这适用于不平衡的树 - 参见上面的“Sub One”。
  • @georg:把{label: 'Sub twelve'}改成{label: 'Sub twelve', children: [{label: 'dingo'}]}就不行了。
  • @georg:将我下面答案中的数据与您的解决方案一起使用,并与我的输出进行比较。
  • @georg:如果树的“右侧”有更多的孩子然后开始/左侧然后你必须为那些空的孩子添加填充...
【解决方案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 ] ]

(我不知道具体如何处理“不同高度”的特性……结构应该是什么样子?)

【讨论】:

    【解决方案3】:

    这应该可以任意组合使用:

    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>

    【讨论】:

      猜你喜欢
      • 2017-07-02
      • 2018-04-20
      • 2019-10-12
      • 1970-01-01
      • 2021-01-23
      • 1970-01-01
      • 2019-11-23
      • 2023-04-04
      • 2021-03-04
      相关资源
      最近更新 更多