字符串转树结构

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: 字符串转树结构

前言


有一个多行字符串,每行开头会用空格来表示它的层级关系,每间隔一层它的空格总数为2,如何将它转为json格式的树型数据?本文就跟大家分享下这个算法,欢迎各位感兴趣的开发者阅读本文。


例如有一个字符串:


const text = `
Language
  JavaScript
    TypeScript
    NodeJS
  HTML
Server
  DataBase
    MongoDB
System
  Linux
  Window
`;


将其转换为有层次结构的json数据后为:


{
    "name":"root",
    "children":[
        {
            "name":"Language",
            "children":[
                {
                    "name":"JavaScript",
                    "children":[
                        {
                            "name":"TypeScript"
                        },
                        {
                            "name":"NodeJS"
                        }
                    ]
                },
                {
                    "name":"HTML"
                }
            ]
        },
        {
            "name":"Server",
            "children":[
                {
                    "name":"DataBase",
                    "children":[
                        {
                            "name":"MongoDB"
                        }
                    ]
                }
            ]
        },
        {
            "name":"System",
            "children":[
                {
                    "name":"Linux"
                },
                {
                    "name":"Window"
                }
            ]
        }
    ]
}


思路分析


乍一看,要对字符串进行处理,好像没有什么比较好的方法,理不出头绪。当我们遇到这种直接从数据结构出发想不出办法的问题时,这时可能就要换个思路了,能否将它转换为另一种数据结构呢?


审题后发现,我们需要的数据元素在字符串中总是独占一行的,那么我们就要对每一行进行处理,此时最好的方式就是将它切割成数组。


那么,我们就以换行符作为切割点来构造数组,如下所示:


[
  "","Language","  JavaScript", "    TypeScript","    NodeJS",   "  HTML","Server","  DataBase","    MongoDB","System","  Linux","  Window",""
]


观察数组中的每个元素后,我们发现最顶层的数据开头无空格,每间隔一层,开头就会多两个空格。按照从前往后的顺序依次读取数据,将后一个数据与其之前的数据进行比较,进而确定他们之间的层次关系。


分析到这里,相信很多开发者已经看出了这个比较方式满足了“后入先出”原则,因此,我们可以用栈来解决这个问题,如下所示:


  • 准备2个栈,一个用于存放每层的字符串,另一个用于存放每层的空格数
  • 默认将root入栈,将它的空格数定为-1


640.png

                             image-20220923074054521


接下来,我们将每个元素逐一入栈,分析下它的过程。如下图所示,我们列举了部分元素的入栈比对过程,通过观察后,总结出了如下几条规律。


  • 获取入栈元素的空格总数
  • 获取栈顶(deepStack)元素,判断入栈元素的空格总数是否大于栈顶元素。
  • 满足条件则获取strStack的栈顶元素,将入栈元素元素放入它的子级
  • 否则,将两个栈的元素依次出栈。直至入栈元素的空格总数比deepStack的栈顶元素大,获取strStack的栈顶元素,将入栈元素元素放入它的子级
  • 将入栈元素以及它的空格总数分别放入对应的栈中
  • 直至所有元素都入栈比对完成,此问题得到解决


640.png

                               image-20220925084748469


注意:为了让读者更直观的看出规律,strStack栈中的元素用字符串直接代替了,实际上栈中存储的数据是一个对象,该对象包含了name属性和children属性。当前入栈元素也会构造成一个对象,得出栈顶元素(deepStack)与入栈元素空格总数的比对结果后,会将入栈元素对象放进栈顶元素(strStack)的children中。


实现代码


经过上面的分析,我们已经得出了完整的实现思路,接下来我们来看下代码的实现。


/**
 * 字符串转树结构
 * @param text
 * @constructor
 */
export function DataConversion(text: string): nodeObj {
  const splitArr = text.split("\n");
  const json = { name: "root" };
  const strStack = new Stack();
  const deepStack = new Stack();
  strStack.push(json);
  deepStack.push(-1);
  for (let i = 0; i < splitArr.length; i++) {
    let str = splitArr[i];
    if (!str) continue;
    // 获取空格总数
    const len = str.lastIndexOf(" ") + 1;
    str = str.replace(/\s/g, "");
    const curObj = { name: str };
    // 寻找当前入栈元素的父层级
    while (len <= deepStack.peek()) {
      deepStack.pop();
      strStack.pop();
    }
    const stackTop: nodeObj = strStack.peek();
    stackTop.children
      ? stackTop.children.push(curObj)
      : (stackTop.children = [curObj]);
    // 元素入栈,继续下一轮的比对
    strStack.push(curObj);
    deepStack.push(len);
  }
  return json;
}


注意:上述代码中声明了一个自定义类型nodeObj以及一个自定义类Stack,完整代码请在示例代码中查看。


最后,我们将开头的例子代入上述代码中,校验下它能否正确解决问题。


const text = `
Language
  JavaScript
    TypeScript
    NodeJS
  HTML
Server
  DataBase
    MongoDB
System
  Linux
  Window
`;
const textJSON = DataConversion(text);
console.log(JSON.stringify(textJSON));


640.png

                           image-20220925095216309


640.png

                                    image-20220925095349312


示例代码


本文用到的代码完整版请移步:


  • DataConversion.ts
  • DataConversion-test.ts


写在最后


至此,文章就分享完毕了。


我是神奇的程序员,一位前端开发工程师。


如果你对我感兴趣,请移步我的个人网站,进一步了解。

  • 公众号无法外链,如果文中有链接,可点击下方阅读原文查看😊


相关文章
|
数据采集 Web App开发 JavaScript
利用Selenium和XPath抓取JavaScript动态加载内容的实践案例
利用Selenium和XPath抓取JavaScript动态加载内容的实践案例
|
8月前
|
自然语言处理 前端开发 安全
指南:Claude 3.7 怎么样?国内如何使用Claude 3.7 Sonnet?
本文主要介绍了Claude 3.7 Sonnet模型的发布教你如何订阅使用Claude 3.7 Sonnect及其新功能,特别是Claude Code工具的推出。
4210 7
|
JavaScript 前端开发 Docker
全栈开发实战:结合Python、Vue和Docker进行部署
【4月更文挑战第10天】本文介绍了如何使用Python、Vue.js和Docker进行全栈开发和部署。Python搭配Flask创建后端API,Vue.js构建前端界面,Docker负责应用的容器化部署。通过编写Dockerfile,将Python应用构建成Docker镜像并运行,前端部分使用Vue CLI创建项目并与后端交互。最后,通过Nginx和另一个Dockerfile部署前端应用。这种组合提升了开发效率,保证了应用的可维护性和扩展性,适合不同规模的企业使用。
841 4
|
算法 前端开发 Java
支撑每秒数百万订单无压力,SpringBoot + Disruptor 太猛了!
本文详细介绍如何通过 Spring Boot 集成 Disruptor 实现每秒处理数百万订单的高性能系统。Disruptor 是一种无锁并发框架,采用环形缓冲区和无锁算法,提供极低延迟和高吞吐量。文章涵盖 Maven 配置、事件工厂、处理器及生产者实现,并通过 REST API 和 Thymeleaf 展示订单创建流程。Disruptor 在高并发场景下表现出色,是解决高性能并发处理的理想方案。
|
消息中间件 缓存 Java
支撑每秒 600 万订单无压力,SpringBoot + Disruptor 太猛了!
【8月更文挑战第28天】在高度竞争且对性能要求极高的互联网时代,如何构建能够支撑海量订单处理的系统,是每一个技术团队都需要面对的挑战。今天,我们将深入探讨SpringBoot结合Disruptor这一高性能队列技术,如何实现每秒支撑600万订单量的壮举,分享其中的技术干货与实战经验。
362 5
|
消息中间件 数据库
消息中间件系列教程(18) -RabbitMQ-基于RabbitMQ解决分布式事务(思想)
消息中间件系列教程(18) -RabbitMQ-基于RabbitMQ解决分布式事务(思想)
309 0
|
Linux Shell
Linux中的realpath命令:深入解析与实用指南
**Linux的`realpath`命令详解** `realpath`用于获取文件或目录的规范化绝对路径,解析相对路径、符号链接及冗余元素。它接受路径输入,返回最短、唯一的绝对路径。支持 `-e`(确保路径存在)、`-m`(允许缺失组件)、`-s`(删除多余斜杠)和`-q`(静默模式)等参数。在脚本中使用能确保路径一致性,但需注意权限和路径检查。了解`pwd`、`find`和`readlink`等命令的用法也有助于选择合适的路径处理工具。
|
消息中间件
RabbitMQ延时队列插件rabbitmq_delayed_message_exchange-3.8.0.ez,有需要的朋友可自取
RabbitMQ延时队列插件rabbitmq_delayed_message_exchange-3.8.0.ez,有需要的朋友可自取
596 0
|
关系型数据库 MySQL
【随手记】MySQL中ROW_NUMBER()、RANK()和DENSE_RANK()函数的用法
【随手记】MySQL中ROW_NUMBER()、RANK()和DENSE_RANK()函数的用法
697 1
|
Java 开发者
Java的三元表达式用法
Java的三元表达式用法
1298 1