JavaScript tree array flattening/and flattened array restore to tree array

For the front-end data requirements, one is to flatten the tree array, and the other is to output the flattened array as a tree array
. The following are two ways to achieve mutual conversion:
the tree array is:

let tree = [
    {
    
    
        "id": 1,
        "name": "1",
        "pid": 0,
        "children": [
            {
    
    
                "id": 2,
                "name": "2",
                "pid": 1,
                "children": []
            },
            {
    
    
                "id": 3,
                "name": "3",
                "pid": 1,
                "children": [
                   {
    
    
                     "id": 4,
                     "name": "4",
                     "pid": 3,
                     "children": []
                   }
                ]
            }
        ]
    }
]

The flattened array is:

let arr = [
 {
    
    id: 1, name: '1', pid: 0},
 {
    
    id: 2, name: '2', pid: 1},
 {
    
    id: 3, name: '3', pid: 1},
 {
    
    id: 4, name: '4', pid: 3},
 {
    
    id: 5, name: '5', pid: 3},
]

1. First, the tree array is flattened

// reduce实现
function treeToArray(tree) {
    
    
  return tree.reduce((res, item) => {
    
    
    const {
    
     children, ...i } = item
    return res.concat(i, children && children.length ? treeToArray(children) : [])
  }, [])
}

2. Convert flattened array to tree

// 先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储
function arrayToTree(items) {
    
    
  const result = [];   // 存放结果集
  const itemMap = {
    
    };  // 
  for (const item of items) {
    
    
    const id = item.id;
    const pid = item.pid;

    if (!itemMap[id]) {
    
    
      itemMap[id] = {
    
    
        children: [],
      }
    }

    itemMap[id] = {
    
    
      ...item,
      children: itemMap[id]['children']
    }

    const treeItem =  itemMap[id];

    if (pid === 0) {
    
    
      result.push(treeItem);
    } else {
    
    
      if (!itemMap[pid]) {
    
    
        itemMap[pid] = {
    
    
          children: [],
        }
      }
      itemMap[pid].children.push(treeItem)
    }

  }
  return result;
}

3. Special case 1 – the field name key value of each array is different

The following methods are based on generator functions:

//Defines a flattenTreeGenerator
generator function that accepts a node and an array containing the key names of child nodes as parameters, and returns a generator object. When the generator is iterated, it will output nodes in depth-first order until the entire tree structure has been traversed.
In the method, flatten the tree structure flattenTreeby calling the generator function and save the result in the array. Plus is to define the function as a generator function. A generator function is a special function that can suspend execution at runtime and resume execution at any time. Generator functions use the keyword to control their execution flow, and they can be continued through the method. When the generator function finishes running, it will automatically return an iterator (iterator), which is used to traverse its output value. In the process of flattening the tree structure, each node of the tree needs to be traversed recursively, and the pause and resume execution characteristics of the generator function make it possible to realize the traversal of the tree structure in a simpler way. In this example, every time the keyword is called, the value of the current node will be output, and then execution will be suspended, waiting for the next call of the method. It should be noted that when using the generator function, you need to use the keyword to traverse the child nodes. Keywords can pass control to sub-generator functions, allowing them to recursively traverse the entire tree structure.flattenTreeGeneratorflattenedTree*
yieldnext()


yieldnext()
yield*yield*

The following method can also flatten the tree array with the same key value (based on Vue2)
Method 1

    *flattenTreeGenerator(node, keys) {
    
    
      yield node;
      for (const key of keys) {
    
    
        if (Array.isArray(node[key])) {
    
    
          for (const child of node[key]) {
    
    
            yield* this.flattenTreeGenerator(child, keys);
          }
        }
      }
    },
    flattenTree(arr, keys) {
    
    
      const result = [];
      for (const node of arr) {
    
    
        for (const item of this.flattenTreeGenerator(node, keys)) {
    
    
          result.push(item);
        }
      }
      return result;
    },

Annotated version

/**
 * 生成器函数,用于扁平化树形结构
 * @param {Object} node - 当前节点
 * @param {Array} keys - 需要扁平化的属性列表
 */
function* flattenTreeGenerator(node, keys) {
    
    
  // 生成器函数,首先 yield 当前节点
  yield node;

  // 遍历需要扁平化的属性
  for (const key of keys) {
    
    
    // 如果当前节点有对应属性且值为数组,则递归遍历数组中的子节点
    if (Array.isArray(node[key])) {
    
    
      for (const child of node[key]) {
    
    
        yield* flattenTreeGenerator(child, keys);
      }
    }
  }
}

/**
 * 扁平化树形结构
 * @param {Array} arr - 原始树形结构
 * @param {Array} keys - 需要扁平化的属性列表
 * @returns {Array} - 扁平化后的结果
 */
function flattenTree(arr, keys) {
    
    
  // 初始化结果数组
  const result = [];

  // 遍历原始树形结构中的每个节点
  for (const node of arr) {
    
    
    // 使用生成器函数扁平化当前节点,并将结果 push 到结果数组中
    for (const item of flattenTreeGenerator(node, keys)) {
    
    
      result.push(item);
    }
  }

  // 返回扁平化后的结果数组
  return result;
}

transfer

      let treeDataSource = [
        {
    
    
          id: "1",
          children: [
            {
    
    
              id: "11",
              name: "aa_sub1",
              desc: "这是一个描述_sub1",
              parentId: "1",
            },
          ],
        },
        {
    
    
          id: "2",
          children: [
            {
    
     id: "22", parentId: "2" },
            {
    
    
              id: "23",
              parentId: "2",
              childrens1: [
                {
    
    
                  id: "233",
                  parentId: "23",
                  childrenss2: [{
    
     id: "2333", parentId: "233" }],
                },
              ],
            },
          ],
        },
      ];
      const keys = ["children",'childrens1','childrenss2'];
      const flattened = this.flattenTree(treeDataSource, keys);

Method 2 – normal recursion
Call the method and generator recursively, the input parameters are: array, key value

    flattenTree(data, keys) {
    
    
      // 定义函数,传入两个参数,第一个是需要扁平化的树形数组,第二个是需要扁平化的关键字
      const result = []; // 定义一个空数组,用来存储扁平化后的结果
      function traverse(node) {
    
    
        // 定义一个名为 traverse 的函数,用于遍历节点
        result.push(node); // 将当前节点添加到结果数组中
        for (const key of keys) {
    
    
          // 遍历传入的关键字数组
          if (Array.isArray(node[key])) {
    
    
            // 如果当前节点的关键字对应的值是一个数组
            for (const child of node[key]) {
    
    
              // 遍历该数组中的子节点
              traverse(child); // 递归调用 traverse 函数,继续遍历子节点
            }
          }
        }
      }
      for (const node of data) {
    
    
        // 遍历原始树形数组中的每个节点
        traverse(node); // 对每个节点递归调用 traverse 函数,遍历所有子节点
      }
      return result; // 返回扁平化后的结果数组
    },

3. Special case 2 – the outer layer is an object

     tree: {
    
    
        id: 1,
        name: "A",
        layer: [
          {
    
    
            id: 2,
            name: "B",
            children: [
              {
    
    
                id: 4,
                name: "D",
                items: [
                  {
    
    
                    id: 6,
                    name: "F",
                    categories: [
                      {
    
    
                        id: 8,
                        name: "H",
                      },
                    ],
                  },
                  {
    
    
                    id: 7,
                    name: "G",
                  },
                ],
              },
              {
    
    
                id: 5,
                name: "E",
              },
            ],
          },
          {
    
    
            id: 3,
            name: "C",
          },
        ],
      },
      flattenedTree: [],
  methods: {
    
    
  	// 方法1:生成器函数
    *flattenTreeGenerator(node, keys) {
    
    
      yield node;
      for (const key of keys) {
    
    
        if (Array.isArray(node[key])) {
    
    
          for (const child of node[key]) {
    
    
            yield* this.flattenTreeGenerator(child, keys);
          }
        }
      }
    },
    // 传入tree和每层数组的key名
    flattenTree() {
    
    
      this.flattenedTree = [
        ...this.flattenTreeGenerator(this.tree, ["layer", "children", "items"]),
      ];
      console.log(this.flattenedTree);
    },
   //-------------------------------------------
    //方法2:正常递归----flattenTree 方法是一个递归函数。
    flattenTree(node, keys) {
    
    
      const result = [node];
      keys.forEach((key) => {
    
    
        if (Array.isArray(node[key])) {
    
    
          result.push(
            ...node[key].flatMap((child) => this.flattenTree(child, keys))
          );
        }
      });
      return result;
    },
    const keys = ["layer", "childre", "items"]
    const flatArr = this.flattenTree(this.tree, keys);
  },

restore to tree array

    // 还原树形结构
    restoreTree(arr, idKey = "id", parentIdKey = "parentId") {
    
    
      const map = {
    
    };
      const roots = [];

      // 将节点按照 id 存储到一个对象中
      for (const node of arr) {
    
    
        map[node[idKey]] = {
    
     ...node, children: [] };
      }

      // 遍历每个节点,将其添加到其父节点的 children 属性中
      for (const node of arr) {
    
    
        const parent = map[node[parentIdKey]];
        if (parent) {
    
    
          parent.children.push(map[node[idKey]]);
        } else {
    
    
          roots.push(map[node[idKey]]);
        }
      }

      return roots;
    },

Flatten —> Restore

<template>
  <div>
    <button @click="GeneratorFlat">生成器方法</button>
    <button @click="openAll">普通递归</button>
  </div>
</template>
<script>
export default {
    
    
  data() {
    
    
    return {
    
    };
  },
  methods: {
    
    
    // 生成器方法扁平化
    GeneratorFlat() {
    
    
      let treeDataSource = [
        {
    
    
          id: "1",
          children: [
            {
    
    
              id: "11",
              name: "aa_sub1",
              desc: "这是一个描述_sub1",
              parentId: "1",
            },
          ],
        },
        {
    
    
          id: "2",
          children: [
            {
    
     id: "22", parentId: "2" },
            {
    
    
              id: "23",
              parentId: "2",
              childrens1: [
                {
    
    
                  id: "233",
                  parentId: "23",
                  childrenss2: [{
    
     id: "2333", parentId: "233" }],
                },
              ],
            },
          ],
        },
      ];
      const keys = ["children", "childrens1", "childrenss2"];
      const flattened = this.flattenTree(treeDataSource, keys);
      console.log(flattened, "生成器递归结果=================");

      const restoredTree = this.restoreTree(flattened);
      console.log(restoredTree, "还原后的树=================");
    },
    // 普通递归
    openAll() {
    
    
      let treeDataSource = [
        {
    
    
          id: "1",
          children: [
            {
    
    
              id: "11",
              name: "aa_sub1",
              desc: "这是一个描述_sub1",
              parentId: "1",
            },
          ],
        },
        {
    
    
          id: "2",
          children: [
            {
    
     id: "22", parentId: "2" },
            {
    
    
              id: "23",
              parentId: "2",
              childrens1: [
                {
    
    
                  id: "233",
                  parentId: "23",
                  childrenss2: [{
    
     id: "2333", parentId: "233" }],
                },
              ],
            },
          ],
        },
      ];
      const keys = ["children", "childrens1", "childrenss2"];
      const flatteneds = this.flattenS(treeDataSource, keys);
      console.log(flatteneds, "普通递归结果=================");
    },
    //正常递归
    flattenS(data, keys) {
    
    
      // 定义函数,传入两个参数,第一个是需要扁平化的树形数组,第二个是需要扁平化的关键字
      const result = []; // 定义一个空数组,用来存储扁平化后的结果
      function traverse(node) {
    
    
        // 定义一个名为 traverse 的函数,用于遍历节点
        result.push(node); // 将当前节点添加到结果数组中
        for (const key of keys) {
    
    
          // 遍历传入的关键字数组
          if (Array.isArray(node[key])) {
    
    
            // 如果当前节点的关键字对应的值是一个数组
            for (const child of node[key]) {
    
    
              // 遍历该数组中的子节点
              traverse(child); // 递归调用 traverse 函数,继续遍历子节点
            }
          }
        }
      }
      for (const node of data) {
    
    
        // 遍历原始树形数组中的每个节点
        traverse(node); // 对每个节点递归调用 traverse 函数,遍历所有子节点
      }
      return result; // 返回扁平化后的结果数组
    },

    // ---------------------------------------------------------
    //生成器递归
    *flattenTreeGenerator(node, keys) {
    
    
      yield node;
      for (const key of keys) {
    
    
        if (Array.isArray(node[key])) {
    
    
          for (const child of node[key]) {
    
    
            yield* this.flattenTreeGenerator(child, keys);
          }
        }
      }
    },
    flattenTree(arr, keys) {
    
    
      const result = [];
      for (const node of arr) {
    
    
        for (const item of this.flattenTreeGenerator(node, keys)) {
    
    
          result.push(item);
        }
      }
      return result;
    },
// --------------------------------------------------------------------
    // 还原树形结构
    restoreTree(arr, idKey = "id", parentIdKey = "parentId") {
    
    
      const map = {
    
    };
      const roots = [];

      // 将节点按照 id 存储到一个对象中
      for (const node of arr) {
    
    
        map[node[idKey]] = {
    
     ...node, children: [] };
      }

      // 遍历每个节点,将其添加到其父节点的 children 属性中
      for (const node of arr) {
    
    
        const parent = map[node[parentIdKey]];
        if (parent) {
    
    
          parent.children.push(map[node[idKey]]);
        } else {
    
    
          roots.push(map[node[idKey]]);
        }
      }

      return roots;
    },
  },
};
</script>

Guess you like

Origin blog.csdn.net/weixin_43811753/article/details/129361256