手写栈【解析数学表达式,重复字符串解码】

目录

解析数学表达式

字符串解码/智能重复


解析数学表达式

const stock = []; // 先进后出,每一次出栈,即一对 ()
const parenthesesPairPosition = {}

// 剔除两侧空格
const removeBlank = (expression, l, r) => {
    while (expression[l] === ' ') {
        l++
    }
    
    while (expression[r] === ' ') {
        r--
    }
    
    return [l, r]
}

// 剔除两侧小括号
const removeParentheses = (l, r) => {
    if (parenthesesPairPosition[l] === r) return [++l, --r]
    
    return [l, r]
}

const parse = (expression, l = 0, r = expression.length - 1, skipSearchTimeOrDivide = false) => {
    let isNumber = true;
    let parenthesesDep = 0; // 记录小括号深度
    let firstTimeOrDivideOperator = null; // 记录遇到的第一个 * / 运算符
    let firstTimeOrDivideOperatorIdx = null; // 记录遇到的第一个 * / 运算符的位置
    
    [l, r] = removeBlank(expression, l, r);
    [l, r] = removeParentheses(l, r);

    for (let i = r; i >= l; i--) {
        const v = expression[i];
        
        if (v === ')') {
            stock.push(i)
            parenthesesDep++
        } else if (v === '(') {
            const last = stock.pop()
            parenthesesPairPosition[i] = last
            parenthesesDep--
        }
        
        // skipSearchTimeOrDivide 为 true 表示表达式是连续的 * /
        if (skipSearchTimeOrDivide && firstTimeOrDivideOperator) {
            return {
                type: firstTimeOrDivideOperator,
                left: parse(expression, l, firstTimeOrDivideOperatorIdx - 1, true),
                right: parse(expression, firstTimeOrDivideOperatorIdx + 1, r),
            }
        }

        if (i === l) {
          if (isNumber) {
            return {
              type: 'number',
              value: Number(expression.slice(l, r + 1).trim()),
            };
          }
          
          if (parenthesesPairPosition[i] === r) {
              return parse(expression, l + 1, r - 1)
          }

          // * / 拆分,需要遍历到最左侧,所里拆分逻辑写这里
          return {
              type: firstTimeOrDivideOperator,
              left: parse(expression, l, firstTimeOrDivideOperatorIdx - 1, true),
              right: parse(expression, firstTimeOrDivideOperatorIdx + 1, r),
          }
        }

        if (/[0-9.]/.test(v) || v === ' ') {
            continue;
        }
        
        isNumber = false;
        
        // parenthesesDep === 0 进行表达式分析的时候要确保是同一层级
        if (parenthesesDep === 0 && (v === '+' || v === '-')) {
            return {
                type: v,
                left: parse(expression, l, i - 1),
                right: parse(expression, i + 1, r),
            }
        }

        if (parenthesesDep === 0 && (v === '*' || v === '/') && !firstTimeOrDivideOperator) {
            firstTimeOrDivideOperator = v
            firstTimeOrDivideOperatorIdx = i
        }
    } 
}

const exec = ast => {
    const recursion = ast => {
        if (ast.type === '+') {
            return recursion(ast.left) + recursion(ast.right)
        } else if (ast.type === '-') {
            return recursion(ast.left) - recursion(ast.right)
        } else if (ast.type === '*') {
            return recursion(ast.left) * recursion(ast.right)
        } else if (ast.type === '/') {
            return recursion(ast.left) / recursion(ast.right)
        } else if (ast.type === 'number') {
            return ast.value
        }
    }
    
    return recursion(ast)
}

let expression = '(1 + (2 - (3 * 4 / 5 + 6 - (7 + 8))) + (9 - 10) * 11 + 12) + 13'

console.log(exec(parse(expression)) === eval(expression))

expression = '3.5 * 4 / 5 + 6'

console.log(exec(parse(expression)) === eval(expression))

字符串解码/智能重复

const decodeString = (s) => {
    let numStack = [];        // 存倍数的栈
    let strStack = [];        // 存 待拼接的str 的栈
    let num = 0;              // 倍数的“搬运工”
    let result = '';          // 字符串的“搬运工”
    for (const char of s) {   // 逐字符扫描
        if (!isNaN(char)) {   // 遇到数字
            num = num * 10 + Number(char); // 算出倍数
        } else if (char == '[') {  // 遇到 [
            strStack.push(result); // result串入栈
            result = '';           // 入栈后清零
            numStack.push(num);    // 倍数num进入栈等待
            num = 0;               // 入栈后清零
        } else if (char == ']') {  // 遇到 ],两个栈的栈顶出栈
            let repeatTimes = numStack.pop(); // 获取拷贝次数
            result = strStack.pop() + result.repeat(repeatTimes); // 构建子串
        } else {                   
            result += char;        // 遇到字母,追加给result串
        }
    }
    return result;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/364177.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【算法详解 | 二分查找】详解二分查找 \ 折半查找高效搜索算法 | 顺序数组最快搜索算法 | 递归循环解决二分查找问题

二分查找 by.Qin3Yu 本文需要读者掌握 顺序表 的操作基础,完整代码将在文章末尾展示。 顺序表相关操作可以参考我的往期博文: 【C数据结构 | 顺序表速通】使用顺序表完成简单的成绩管理系统.by.Qin3Yu 文中所有代码使用 C 举例,且默认已使用…

Windows存储空间不足局域网文件共享 Dism备份系统空间不足

问题情景 在日常使用中难免遇到Windows的空间不足的情况,常用办法是清理垃圾释放空间,部分场景例如我们需要使用Dism备份完整系统,所以需要非常大的存储空间不够,如果空间不够什么才是最有效的方案呢? 我们假设身边没有…

【HarmonyOS 4.0 应用开发实战】TypeScript入门之模块化详讲

个人名片: 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻‍❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️…

如何在Raspberry Pi上启用SSH并结合cpolar内网穿透实现公网远程访问本地树莓派

文章目录 如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar4.2 cpolar进行token认证4.3 配置cpolar服务开机自启动4.4 查看映射到公网的隧道地址4.5 ssh公网…

Oracle函数使用

ROW_NUMBER函数 ROW_NUMBER() OVER(PARTITION BY column1 ORDER BY column2 DESC) -- 根据column1分组按column2降序排序生成序号,序号由小到大,会生成一个唯一的序号 -- 例如column2中有两列值都为1,那他们的序号会有一个在上一个在下ROW_NUMBER() OVER(ORDER BY …

Redis-持久机制

文章目录 为什么有持久化什么是持久化RDB文件创建SAVEBGSAVE 文件载入 优缺点 AOF日志步骤 对比数据恢复 总结 Redis是一个开源的内存数据结构存储系统,被广泛应用于Web应用中,可以用作数据库和缓存服务器。它具有高性能、高并发、高可用性等特点&#x…

计网——应用层

应用层 应用层协议原理 网络应用的体系结构 客户-服务器(C/S)体系结构 对等体(P2P)体系结构 C/S和P2P体系结构的混合体 客户-服务器(C/S)体系结构 服务器 服务器是一台一直运行的主机,需…

零基础小白到底要不要学习鸿蒙,看完这篇再决定吧~

随着华为鸿蒙系统的问世,不少技术小白在是否学习鸿蒙的问题上犹豫不决。鸿蒙作为华为自主研发的操作系统,拥有许多独特的技术优势和市场前景。但对于小白来说,是否值得投入时间和精力去学习鸿蒙开发呢? 1.鸿蒙系统开发&#xff1…

短视频去水印教程,免费一键获取视频、图片、文案【迅风去水印】

自媒体行业的蓬勃发展,让越来越多的创作者涌入其中。然而,剪辑过程中常常遭遇到一个令人头疼的问题,那就是视频或图片上的水印。这些水印不仅会影响到作品的美感,还可能侵犯到版权。为了帮大家解决这一难题,分享一个免…

新手不会Git也能玩Github吗?

新手不会Git也能玩Github吗? 前言使用Github的准备步骤使用一种访问外网资源的方法(这一步才是新手最容易)注册账号 创建一个自己的仓库创建完仓库后的界面 搜索你想要的代码类型以搜索坦克大战为例以下载烟花代码为例 总结 前言 说到Github&…

认识 SYN Flood 攻击

文章目录 1.什么是 SYN Flood 攻击?2.半连接与全连接队列3.如何防范 SYN Flood 攻击?参考文献 1.什么是 SYN Flood 攻击? SYN Flood 是互联网上最原始、最经典的 DDoS(Distributed Denial of Service)攻击之一。 SYN…

使用STM32的FMC/FSMC接口实现多路数据传输和并发操作的设计与应用

在基于STM32的系统中,FMC(Flexible Memory Controller)/FSMC(Flexible Static Memory Controller)接口可以用于实现多路数据传输和并发操作。通过合理的设计和应用,我们可以提高系统的数据处理速度和效率。…

XML详解

XML 简介 概述:Extensible Markup Language 可扩展标记语言 可扩展:标签都是自定义的。 功能 数据存储:XML 可以用来存储结构化数据,包括文本、数字、日期等各种类型的数据数据交换:XML 可以作为一种通用的数据交换格…

Elasticsearch(ES) 下载添加IK分词器

上文 通过Web请求对 Elasticsearch(ES) 进行索引的 增删查 操作 我们通过web请求 创建了一个索引 但 目前 我们的索引是不具有分词效果的 我们并没有为索引指定分词器 所以 我们目前加进去的数据 就会保持原样 没有分词的能力 我们执行get查询操作 会发现一个 mappings字段 它…

如何访问 Oracle OKE 集群

OKE是Oracle Cloud提供的托管Kubernetes服务,为用户提供强大而灵活的容器编排平台。在本文中,我们将详细介绍如何有效地与OKE集群进行交互,包括访问集群的不同方式、管理访问权限以及执行常见操作的步骤。 1 安装oci命令 1.1 在Oracle Linux…

算法总结归纳(第十天)(动态规划第三部分)(线性dp)

目录 一、简单线性dp 1、最长递增子序列 ①、题目描述 ②、解题思路 ③、代码实现 2、最长连续递增序列 ①、题目描述 ②、解题思路 ③、代码实现 3、最长重复子数组 ①、题目描述 ②、解题思路 ③、代码实现 4、最长公共子序列 ①、题目描述 ②、解题思路 ③、…

Spring Boot 整合 Redis 使用教程

作为开发者,相信大家都知道 Redis 的重要性。Redis 是使用 C 语言开发的一个高性能键值对数据库,是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。 Redis 以超…

openharmony开发版应用安装签名

配置签名信息 应用/服务在真机设备上运行,需要提前为应用/服务进行签名,DevEco Studio为开发者提供了自动化签名方案,可以一键完成应用/服务签名。具体操作如下: 单击File > Project Structure > Project > Signing Con…

Pandas进阶--map映射,分组聚合和透视pivot_table详解

文章目录 1.Pandas的map映射(1)映射(2)map充当运算工具 2.数据分组和透视(1)分组统计 - groupby功能 是pandas最重要的功能(2)聚合agg 3.透视表pivot_table(1&#xff09…

【大厂AI课学习笔记】1.3 人工智能产业发展(4)——泛在的人工智能

人工智能走向泛在。 泛在,就是广泛存在。(下图来自腾讯AI课。) 没办法,被百度抛弃了,想学习,课程打不开,只好投想腾讯的怀抱。 之前考过腾讯云的认证,课程做的还是条理很清晰。 主…