刷算法没用?来看看这个需求

106 阅读3分钟

平时刷算法都是为了面试,除此之外,感觉刷算法一点用都没有。但是,直到我开发碰上这个需求,不禁感叹,刷算法还是很有用的。

业务需求

看到这个图,我想第一感觉就是直接原生table撸就完事了。巴特!需求是表格的表头和表体都是动态配置的。所以,要根据配置的数据来渲染表格,涉及到跨行就难搞了,同时,还要支持自定义配置列宽度。

解决方案的前置知识

利用colgroupcolwidth属性,就可以对每列进行宽度控制,该属性虽已废弃,但是在浏览器中还有较好的支持(查看支持的浏览器版本)

table的跨行规则,例如,表格有两行五列,第一列跨两行,则第一行的数据为5条,第二行的数据为4条,因为有一个跨行

解决方案

由于后台返回来的数据是一个树结构,而我们需要将树转成一条条符合table渲染的数据结构,树的深度代表表格有多少列。所以,这里就很容易联想到了树的全路径遍历算法来处理树结构。举个例子🌰 利用树的全路径遍历算法得到下图结果:

那么,根据table的跨行规则,我们需要进行合并,需要得出以下数据结果:
结果生成的表格如下:

解释: 因为1是树的根节点,所以他是跨的最多行,也就是5行。那么生成的5条数据中就不能出现1,对算法进行稍微的改造,将遍历过的节点加上标识,这样在遍历路径的时候,有标识的节点就不添加当前行中。

至于4后边的跨5行,可以根据id去重,将id相同的合并,也就是4-1到4-5的子项都是同样的数据id,在遍历是记录id去重。

代码实现

表格框架此处选择将表头和表体分离的方式,方便以后扩展其他功能(demo用vue3+ts演示),mock数据见资源导航

<template>
  <div class="table">
    <table border="1" align="center" class="mainTable">
      <colgroup>
        <col v-for="(width, index) in widths" :key="index" :width="width" />
      </colgroup>
      <thead>
        <tr>
          <th ref="colRefs" v-for="(item, index) in headerData" :key="index">
            {{ item.title }}
          </th>
        </tr>
      </thead>
    </table>
    <table border="1" align="center" class="mainTable">
      <colgroup>
        <col v-for="(width, index) in widths" :key="index" :width="width" />
      </colgroup>
      <tbody align="center">
        <template v-for="(item, index) in bodyData">
          <tr v-for="(item1, index1) in numArr[index]" :key="item.id + item1">
            <td v-for="(item2, index2) in rowData[index][index1]" :rowspan="item2.maxRow" :key="index2">
              {{ item2.content }}
            </td>
          </tr>
        </template>
      </tbody>
    </table>
  </div>
</template>

逻辑部分实现:

<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import data from "./mock1.json";
const headerData = reactive<HeaderData[]>(data.headerData)
const bodyData = reactive(data.bodyData)
const numArr = reactive<number[][]>([])
const rowData = reactive<Array<TreeNode[][]>>([])
const widths = reactive<(string | number)[]>([])
let idSets = new Set()
const colRefs = ref([])
interface HeaderData {
  id: string,
  title: string,
  type: number,
  width?: string | number
}
interface TreeNode {
  id: string,
  content: string,
  contentValue: null,
  maxRow: number,
  children: TreeNode[],
  isTraverse?: boolean
}
const getRowData = (data: TreeNode[]) => {
  //遍历树的所有路径
  const getAllPath = (tree: TreeNode[]):TreeNode[][] => {
    const paths = []; //记录路径
    for (let i = 0; i < tree.length; i++) {
      //遍历同层的所有节点
      if (tree[i].children && tree[i].children.length) {
        //如果有子节点便继续深入,直到到达叶子节点
        const res = getAllPath(tree[i].children); 
        for (let j = 0; j < res.length; j++) {
          if (!tree[i].isTraverse && !idSets.has(tree[i].id)) {
            // 添加过的加标识 同时将id放到Set结构中去重 子节点返回后将其返回的路径与自身拼接
            idSets.add(tree[i].id)
            paths.push([tree[i], ...res[j]]);
            tree[i].isTraverse = true;
          } else {
            paths.push([...res[j]]);
          }
        }
      } else {
        if (!tree[i].isTraverse) {
          //没有子节点的话,直接将自身拼接到paths中
          paths.push([tree[i]]); 
          tree[i].isTraverse = true;
        }
      }
    }
    return paths;
  };
  // 遍历每个大项下边的所有路径(第一列每条数据视为一个大项也就是一个树结构)
  for (let item of data) {
    let arr = [item];
    rowData.push(getAllPath(arr));
  }
}
const createTable = (data:TreeNode[]) => {
  // 得到每条数据行数组- 该数组也可做计算行序号用
  let k = 0;
  while (k < data.length) {
    let arr1 = [];
    let i = 1;
    if (data[k].maxRow) {
      for (let j = 0; j < data[k].maxRow; j++) {
        arr1.push(i);
        i++;
      }
    } else {
      arr1.push(1);
    }
    k++;
    numArr.push(arr1);
  }
  getRowData(data);
}
createTable(bodyData)
onMounted(() => {
    //得到表头每列的宽度-如果没有自定义宽度就取元素宽度
    colRefs.value.forEach((item:HTMLElement, index) => {
      const width = headerData[index].width
      if (width) {
        widths.push(width);
      } else {
        widths.push(item.offsetWidth);
      }
    });
})
</script>

说明:maxRow代表当前树的层级,也就是最大跨行数。maxRow可以通过树的广度优先遍历获取,此处直接表示(交给后端,让后端处理)

总结

算法在我们平时并不是一个关注点,也不会专门去研究,但是,一旦我们遇到难一点的需求需要处理数据的时候,算法就尤为重要了,可以为我们提供很多思路,快速的得出解决方案。
由此,在平时只要稍微关注下算法,了解一些数据结构,刷刷算法,在关键时候可以省去很多的时间,剩下的时间摸鱼它不香嘛

资源导航

demo地址