在数据展示中,我们经常需要对表格进行合并操作,特别是层级合并,以便更清晰地展示具有层次结构的数据。
我实现了递归的算法,用 AI 优化为基于队列的形式。
最后用 AI 生成了一个测试工具
效果
算法实现
算法一:基于队列的迭代实现
第一种算法采用迭代方式,使用队列来管理需要处理的区间:
/**
* 表格层级合并工具函数
* 返回合并单元格的下标数组
* @param {Array} data - 要合并的表格数据
* @param {Array} columns - 表格所有列名(顺序决定了表格中列的排列)
* @param {Array} mergeColumns - 需要参与合并的列名数组
* @returns {Array} - 合并单元格的下标数组,每个元素格式为 {row: 开始行, col: 列索引, rowspan: 行数}
*/
export function getMergedCells(data, columns, mergeColumns) {
const mergedCells = [];
// 使用队列来管理需要合并的区间,每个元素包含起始位置、结束位置和当前深度
const mergeQueue = [{ start: 0, end: data.length, depth: 0 }];
// 循环处理队列中的每个区间
while (mergeQueue.length > 0) {
const { start, end, depth } = mergeQueue.pop();
// 如果深度超过需要合并的列数,则停止处理
if (depth >= mergeColumns.length) continue;
// 获取当前深度需要合并的列名
const col = mergeColumns[depth];
let pos = start;
// 在当前区间内查找可以合并的连续相同值
while (pos < end) {
let i = pos;
// 找到连续相同值的结束位置
while (i < end - 1 && data[i][col] === data[i + 1][col]) i++;
// 如果有连续相同值(长度大于1)
if (i > pos) {
// 获取列在表格中的索引位置
const colIndex = columns.indexOf(col);
// 记录合并信息
mergedCells.push({ row: pos, col: colIndex, rowspan: i - pos + 1 });
// 如果还有更深层级需要合并,则将当前合并区间加入队列
if (depth < mergeColumns.length - 1) {
mergeQueue.push({ start: pos, end: i + 1, depth: depth + 1 });
}
}
// 移动到下一个不相同的值开始的位置
pos = i + 1;
}
}
return mergedCells;
}
算法二:基于递归的实现
第二种算法采用递归方式实现:
/**
* 表格层级合并工具函数
* 返回合并单元格的下标数组
* @param {Array} data - 要合并的表格数据
* @param {Array} columns - 表格所有列名(顺序决定了表格中列的排列)
* @param {Array} mergeColumns - 需要参与合并的列名数组
* @returns {Array} - 合并单元格的下标数组,每个元素格式为 {row: 开始行, col: 列索引, rowspan: 行数}
*/
export function getMergedCells(data, columns, mergeColumns) {
// 参数校验,确保必要的参数存在且不为空
if (
!data ||
!data.length ||
!columns ||
!columns.length ||
!mergeColumns ||
!mergeColumns.length
) {
return [];
}
// 存储合并单元格信息的数组
const mergedCells = [];
/**
* 递归处理函数,用于查找和记录需要合并的单元格
* @param {Number} start - 当前处理区间的起始行索引
* @param {Number} end - 当前处理区间的结束行索引(不包含)
* @param {Number} depth - 当前处理的合并列深度(在mergeColumns中的索引)
*/
const mergeRecursive = (start, end, depth) => {
// 如果深度超过需要合并的列数,则停止递归
if (depth >= mergeColumns.length) return;
// 获取当前深度需要处理的列名
const col = mergeColumns[depth];
// 初始化当前位置为区间起始位置
let pos = start;
// 遍历当前区间的所有行
while (pos < end) {
let i = pos;
// 查找连续相同值的范围
// 如果当前行和下一行在当前列的值相同,则继续向后查找
while (i < end - 1 && data[i][col] === data[i + 1][col]) {
i++;
}
// 如果找到了连续相同的值(范围大于1行)
if (i > pos) {
// 获取列在表格中的索引位置
const colIndex = columns.indexOf(col);
// 记录合并信息:起始行、列索引、跨越行数
mergedCells.push({
row: pos,
col: colIndex,
rowspan: i - pos + 1,
});
// 如果还有更深层级需要处理,则递归处理子区间
if (depth < mergeColumns.length - 1) {
mergeRecursive(pos, i + 1, depth + 1);
}
}
// 移动到下一组不相同的值开始的位置
pos = i + 1;
}
};
// 从第0行开始,到数据末尾,深度为0开始递归处理
mergeRecursive(0, data.length, 0);
return mergedCells;
}
测试输出
// 使用示例
const sampleData = [
{ category: '电子产品', region: '华东', year: 2022, quarter: 'Q1', sales: 120000 },
{ category: '电子产品', region: '华东', year: 2022, quarter: 'Q2', sales: 150000 },
{ category: '电子产品', region: '华东1', year: 2022, quarter: 'Q3', sales: 180000 },
{ category: '电子产品', region: '华东1', year: 2022, quarter: 'Q4', sales: 210000 },
{ category: '电子产品', region: '华南', year: 2022, quarter: 'Q1', sales: 90000 },
{ category: '电子产品', region: '华南', year: 2022, quarter: 'Q2', sales: 110000 },
{ category: '服装', region: '华东', year: 2022, quarter: 'Q1', sales: 80000 },
{ category: '服装', region: '华东', year: 2022, quarter: 'Q2', sales: 95000 },
{ category: '服装', region: '华南', year: 2022, quarter: 'Q1', sales: 70000 },
{ category: '服装', region: '华南', year: 2022, quarter: 'Q2', sales: 85000 },
{ category: '食品', region: '华东', year: 2023, quarter: 'Q1', sales: 50000 },
{ category: '食品', region: '华东', year: 2023, quarter: 'Q2', sales: 60000 }
];
// 指定合并列的顺序
const mergeColumns = ["category", "region", "year", "quarter"];
// 获取合并单元格的下标数组
const mergedCells = getMergedCells(sampleData, mergeColumns);
console.log("合并单元格下标数组:", mergedCells);
/**
合并单元格下标数组: [
{ row: 0, col: 0, rowspan: 6 },
{ row: 0, col: 1, rowspan: 2 },
{ row: 0, col: 2, rowspan: 2 },
{ row: 2, col: 1, rowspan: 2 },
{ row: 2, col: 2, rowspan: 2 },
{ row: 4, col: 1, rowspan: 2 },
{ row: 4, col: 2, rowspan: 2 },
{ row: 6, col: 0, rowspan: 4 },
{ row: 6, col: 1, rowspan: 2 },
{ row: 6, col: 2, rowspan: 2 },
{ row: 8, col: 1, rowspan: 2 },
{ row: 8, col: 2, rowspan: 2 },
{ row: 10, col: 0, rowspan: 2 },
{ row: 10, col: 1, rowspan: 2 },
{ row: 10, col: 2, rowspan: 2 }
]
**/
交互式测试工具
为了方便对比这两种算法的效果,我们开发了一个交互式测试工具,具有以下功能:
- 数据编辑器:可以自定义测试数据
- 列配置:可以指定表格的列顺序
- 合并列配置:可以指定需要进行层级合并的列
- 预设数据:提供多种典型数据场景进行测试
- 实时对比:同时展示两种算法的合并结果并进行比对