渲染2D数组作为分层html表

时间:2020-03-15 21:08:33

标签: javascript algorithm

我有这样的数据结构,顺序可以是随机的

const table = [
  ['A', 'a', 0],
  ['A', 'a', 1],
  ['A', 'b', 0],
  ['B', 'a', 0],
  ['B', 'b', 1],
  ['B', 'b', 0],
]

这应该呈现为html这样的分层表

<table>
  <tr><td rowspan="3">A</td><td rowspan="2">a</td><td>0</td></tr>
  <tr><td>1</td></tr>
  <tr><td>b</td><td>0</td></tr>
  <tr><td rowspan="3">B</td><td>a</td><td>0</td></tr>
  <tr><td rowspan="2">b</td><td>0</td></tr>
  <tr><td>1</td></tr>
</table>

我想知道什么是解决此问题的有效方法。考虑过将列表转换成树然后再返回到列表,但是我不确定这是解决此问题的最佳方法。

2 个答案:

答案 0 :(得分:4)

将每个项目转换为具有rowCount的对象,并使用0表示前一个项目相同,因此不要显示它。我不知道您正在使用哪种视图绑定库,但是在Angular,Vue或React中,绑定到表真的很容易。如果使用纯JavaScript进行操作,则必须循环创建DOM元素。

const table = [
  ['A', 'a', 0],
  ['A', 'a', 1],
  ['A', 'b', 0],
  ['B', 'a', 0],
  ['B', 'b', 1],
  ['B', 'b', 0],
];

const rowSpan = rows =>
  rows.map((row, rowIndex) =>
    row.map((item, colIndex) => ({ data: item, rowSpan: countRows(rows, rowIndex, colIndex) }))
  );

const countRows = (rows, rowIndex, colIndex) => {
  if (rowIndex > 0 && rows[rowIndex - 1][colIndex] === rows[rowIndex][colIndex]) {
    return 0;
  }
  let count = 1;
  while (count + rowIndex < rows.length && rows[rowIndex + count][colIndex] === rows[rowIndex][colIndex]) {
    count++;
  }
  return count;
};

const tableElement = document.createElement('table');

rowSpan(table).forEach(row => {
  const tr = document.createElement('tr');
  tableElement.appendChild(tr);
  row.forEach(item => {
    if (item.rowSpan) {
      const td = document.createElement('td');
      td.rowSpan = item.rowSpan;
      td.innerHTML = item.data.toString();
      tr.appendChild(td);
    }
  });
});

document.body.appendChild(tableElement);
td {
  border: 1px solid black;
  vertical-align: top;
  padding: 3px 10px;
}

答案 1 :(得分:0)

这似乎是一个很好的问题。我的回答可能有点长,所以请忍受我。

我的方法基本上是在JS级别处理所有数据操作,然后将表一次性全部附加到DOM,而不会在整个操作逻辑中弄乱DOM元素。

基本思想是转换

var table = [ ['A', 'a', 0]
            , ['A', 'a', 1]
            , ['A', 'b', 0]
            , ['B', 'a', 0]
            , ['B', 'b', 1]
            , ['B', 'b', 0],
            ];

进入

[ [ { text: 'A', span: 3, bool: true  }
  , { text: 'a', span: 2, bool: false }
  , { text: 0,   span: 1, bool: false }
  ]
,
  [ { text: 'A', span: 0, bool: true  }
  , { text: 'a', span: 0, bool: false }
  , { text: 1,   span: 1, bool: false }
  ]
,
  [ { text: 'A', span: 0, bool: true  }
  , { text: 'b', span: 1, bool: false }
  , { text: 0,   span: 1, bool: false }
  ]
,
  [ { text: 'B', span: 3, bool: true  }
  , { text: 'a', span: 1, bool: false }
  , { text: 0,   span: 1, bool: false }
  ]
,
  [ { text: 'B', span: 0, bool: true  }
  , { text: 'b', span: 2, bool: false }
  , { text: 1,   span: 1, bool: false }
  ]
,
  [ { text: 'B', span: 0, bool: true  }
    { text: 'b', span: 0, bool: false }
    { text: 0,   span: 1, bool: false }
  ]
]

为了获得这些数据,我在两个地方使用了一种稍微不合常规的方法,我相信它应该已经存在于JS中,但是...我们必须自己做。 Array.prototype.zipWith()将使用一个函数和另一个数组,并将提供的函数应用于两个数组的相应元素。如果数组的长度不同,则最短的所有数组都用完时它将停止。如

[1,2,3,4].zipWith((x,y) => x + y, [5,6,7])   // [6,8,10]
[1,2,3].zipWith((x,y) => x + y, [5,4,3,2,1]) // [6,6,6]

这可能类似于

[].zipWith || Object.defineProperty( Array.prototype
                                   , "zipWith"
                                   , {value: function(f,a){
                                               var l = a.length;
                                               return this.reduce((r,e,i) => i < l ? ( r.push(f(e,a[i]))
                                                                                     , r
                                                                                     )
                                                                                   : r, []);
                                             }
                                     }
                                   );

我们可以将以下代码分为reduce级别的两个return处理阶段,并进行相应的研究。

  1. reduce阶段提供临时数据。
  2. reduce阶段创建包含表的DOM片段。

您可能已经注意到我的缩进也有些不同,但是请相信我,一旦将代码复制粘贴到您喜欢的文本编辑器上,它将更容易阅读...我希望:)

因此,这里出现的代码具有稍微不同的数据集,以便在“随机性”开始时显示其行为...如果我正确理解OP。

[].zipWith || Object.defineProperty( Array.prototype
                                   , "zipWith"
                                   , {value: function(f,a){
                                               var l = a.length;
                                               return this.reduce((r,e,i) => i < l ? (r.push(f(e,a[i])),r)
                                                                                   : r, []);
                                             }
                                     }
                                   );

var table = [ ['A', 'a', 1]
            , ['A', 'a', 1]
            , ['A', 'b', 1]
            , ['B', 'b', 1]
            , ['B', 'a', 0]
            , ['B', 'b', 1]
            , ['B', 'b', 1]
            , ['B', 'b', 0]
            ];

function makeTable([r,...rs]){

  var f = new DocumentFragment(),
      t = f.appendChild(document.createElement("table")),
      z = (f,s) => f.text === s ? ( f.span += 1
                                  , f
                                  )
                                : ( f.bool = false
                                  , { text: s
                                    , span: 1
                                    , bool: false
                                    }
                                  );
  t.id ="table-with-spans";
  
  return rs.reduce( function(rs,r){
                      var b = rs[rs.length-1].zipWith((f,s) => f.text === s && f.bool, r)
                                             .some(b => b);
                      
                      return ( rs.push(b ? rs[rs.length-1].zipWith(z,r)
                                         : r.map(d => ({ text: d
                                                       , span: 1
                                                       , bool: true
                                                       })
                                                )
                                      )
                             , rs
                             )
                    }
                  , [r.map(d => ({ text: d
                                 , span: 1
                                 , bool: true
                                 })
                          )
                    ]
                  )
           .reduce( function(t,r){
                      var tds = r.filter(td => td.span),
                          tde;
                      t.appendChild(document.createElement("tr"))
                       .append(...tds.map(function(td){
                                            tde = document.createElement("td");
                                            tde.textContent = td.text;
                                            tde.rowSpan     = td.span;
                                            td.span = 0;
                                            return tde;
                                          })
                              );
                        return t;
                    }
                  , t
                  ).parentNode;
}

document.getElementById("table-container")
        .appendChild(makeTable(table));
#table-with-spans {
  width          : 50vw;
  text-align     : center;
  border         : 1px solid black;
  border-collapse: separate;
  border-spacing : 2px;
  margin         : 0 auto;
}

tr{
  height : 2.5vw;
}

td {
  padding: 2px;
  border: 1px solid black;
  vertical-align: middle;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>repl.it</title>
    <link href="style.css" rel="stylesheet" type="text/css" />
    <script src="script.js" async></script>
  </head>
  <body>
    <div id="table-container">
    </div>
  </body>
</html>