文本分词与带引号短语保留的JavaScript实现

文本分词与带引号短语保留的javascript实现

本文详细介绍了如何在JavaScript中将文本字符串拆分为独立的词语,同时确保双引号内的短语作为一个整体被保留。通过采用有限状态机(FSM)的原理,我们能够健壮地处理各种复杂的输入情况,包括多余空格、引号内部的空格以及引号缺失等边缘情况,最终输出一个包含所有独立词语和完整短语的数组。

文本解析挑战:词语与短语的分离

在处理用户输入或文本数据时,我们经常需要将一个长字符串拆分成独立的“词语”或“标记”(tokens)。然而,简单的空格分割(例如 string.split(‘ ‘))往往不足以应对复杂场景,特别是当我们需要将双引号内的短语视为一个不可分割的整体时。例如,对于输入字符串 “on time” flight,我们期望得到的结果是 [“on time”, “flight”],而不是 [“”on”, “time””, “flight”]。

传统的 split(‘ ‘) 方法会错误地将 “on time” 分割成 “”on 和 time””,这显然不符合我们的预期。为了解决这一问题,我们需要一种更智能的解析策略,能够识别并处理双引号的边界。

有限状态机(FSM)解析策略

处理这类字符串解析问题的强大工具是有限状态机(Finite-State Machine, FSM)。FSM通过定义一组“状态”和“状态转换规则”,来模拟解析过程。在我们的场景中,可以定义两种主要状态:

word 状态:表示当前正在解析一个普通的词语(不在双引号内)。phrase 状态:表示当前正在解析一个双引号内的短语。

解析器会根据当前字符和当前状态,决定是继续累积当前词语/短语,还是切换到另一个状态,并完成当前词语/短语的收集。

立即学习“Java免费学习笔记(深入)”;

FSM的实现细节

下面我们将通过一个JavaScript函数 splitToWordsWithQuotes 来实现这个FSM解析器:

function splitToWordsWithQuotes(str) {    let mode = null; // null: 初始状态/空白状态, 'word': 正在解析词语, 'phrase': 正在解析短语    const words = []; // 存储解析出的词语和短语    let currentToken = ''; // 临时存储当前正在构建的词语或短语    // 辅助函数:完成当前词语/短语的收集,并重置 currentToken    const completeToken = () => {        if (currentToken.length > 0) { // 只有当 currentToken 非空时才添加            words.push(currentToken);            currentToken = '';        }    };    for (let i = 0; i < str.length; i++) {        const char = str[i];        // 1. 初始状态或空白状态 (mode 为 null)        if (mode === null) {            if (char === ' ') {                continue; // 跳过前导空格            }            if (char === '"') {                mode = 'phrase'; // 遇到双引号,进入短语模式            } else {                currentToken += char;                mode = 'word'; // 遇到非空格非引号字符,进入词语模式            }            continue;        }        // 2. 处理双引号字符        if (char === '"') {            completeToken(); // 遇到引号,意味着当前词语/短语结束            // 切换模式:如果当前是词语模式,则下一个是短语;如果是短语模式,则下一个是词语。            // 实际上,这里是结束当前短语模式,并准备进入下一个词语模式 (或等待下一个引号进入短语模式)            // 修正:当遇到引号时,总是结束当前模式,并将模式设为 null,等待下一个字符决定新模式。            // 更精确的做法是:如果当前是 phrase 模式,引号表示短语结束,回到 null 模式。            // 如果当前是 word 模式,引号表示词语结束,进入 phrase 模式。            // 考虑到FSM的简洁性,这里可以简化为:引号总是完成当前token,并切换模式。            // 如果当前是 'word' 模式,遇到 '"',表示词语结束,进入 'phrase' 模式。            // 如果当前是 'phrase' 模式,遇到 '"',表示短语结束,回到 'null' 模式(等待下一个token的开始)。            mode = (mode === 'phrase') ? null : 'phrase';            continue;        }        // 3. 处理空格字符        if (char === ' ') {            if (mode === 'phrase') {                currentToken += ' '; // 在短语模式下,空格是短语内容的一部分                continue;            }            // 在词语模式下,空格表示当前词语结束            completeToken();            mode = null; // 回到 null 模式,等待下一个词语或短语的开始            continue;        }        // 4. 处理其他字符 (字母、数字等)        currentToken += char; // 将字符添加到当前词语/短语中        // 如果当前是 null 模式,且遇到非空格非引号字符,则进入 'word' 模式        if (mode === null) {            mode = 'word';        }    }    // 循环结束后,确保最后一个词语/短语被收集    completeToken();    return words;}

示例代码与运行效果

为了更好地理解上述FSM的工作原理,我们提供一些测试用例:

const myStr = '    "hello guys", some     words with "quotes inside" some spaces inside " please keep quoted words as one "phrase / word" end-of-line ';const myWrongStr = '"hello guys", some words" with "quotes inside" please keep quoted words as one "phrase / word" '; // 包含未闭合引号的示例console.log('--- 正常输入示例 ---');console.log('输入:', myStr);console.log('输出:', splitToWordsWithQuotes(myStr));// 预期输出: ["hello guys", "some", "words", "with", "quotes inside", "some", "spaces", "inside", "please", "keep", "quoted words as one", "phrase / word", "end-of-line"]console.log('--- 包含未闭合引号的示例 ---');console.log('输入:', myWrongStr);console.log('输出:', splitToWordsWithQuotes(myWrongStr));// 预期输出: ["hello guys", "some", "words" with "quotes inside", "please", "keep", "quoted words as one", "phrase / word"]// 注意:对于未闭合的引号,FSM会将其视为普通字符,或将后续内容都视为短语的一部分,直到遇到下一个引号或字符串结束。console.log('--- 原始问题示例 ---');const originalInput = '"on time" "flight"';console.log('输入:', originalInput);console.log('输出:', splitToWordsWithQuotes(originalInput));// 预期输出: ["on time", "flight"]

代码解析:

mode 变量:是FSM的核心,它跟踪当前解析器所处的状态。null:表示当前不在解析任何词语或短语,可能处于多个词语/短语之间的空白区域。’word’:表示正在收集一个普通词语。’phrase’:表示正在收集一个双引号内的短语。words 数组:用于存储最终解析出的所有词语和短语。currentToken 变量:一个字符串缓冲区,用于累积当前正在解析的词语或短语的字符。completeToken() 辅助函数:当一个词语或短语完成时(例如遇到空格或引号),此函数将 currentToken 添加到 words 数组中,并清空 currentToken 以便开始收集下一个。循环遍历字符:代码逐个字符地检查输入字符串。状态转换逻辑遇到空格:如果在 ‘phrase’ 模式下,空格被视为短语的一部分,直接添加到 currentToken。如果在 ‘word’ 模式下,空格表示当前词语结束,调用 completeToken() 并将 mode 重置为 null。遇到双引号 “:无论当前处于何种模式,双引号都意味着当前 currentToken 的结束。调用 completeToken()。然后,如果之前是 ‘phrase’ 模式,则双引号表示短语结束,mode 切换回 null(等待下一个词语/短语)。如果之前是 null 或 ‘word’ 模式,双引号表示开始一个新短语,mode 切换到 ‘phrase’。遇到其他字符:这些字符总是添加到 currentToken。如果 mode 是 null,则表示开始了一个新词语,将 mode 设置为 ‘word’。循环结束后的处理:在 for 循环结束后,需要再次调用 completeToken(),以确保字符串末尾的任何未完成的词语或短语被正确收集。

注意事项与扩展

未闭合的引号:当前的FSM实现对于未闭合的引号,会将后续内容都视为短语的一部分,直到字符串结束或遇到下一个引号。如果需要更严格的错误处理(例如抛出错误或记录警告),可以在 phrase 模式下,在循环结束后检查 mode 是否仍为 ‘phrase’。转义引号:如果短语内部可能包含转义的双引号(例如 “),当前的FSM不会将其视为普通字符,而是会错误地将其解释为短语的结束。处理这种情况需要更复杂的FSM逻辑,例如引入一个“转义字符”状态。性能:对于非常长的字符串,逐字符遍历的FSM通常比正则表达式具有更好的性能和可读性,尤其是在处理复杂嵌套或状态依赖的解析任务时。后处理:本教程的FSM旨在将字符串解析成一个词语/短语数组。如果需要将此数组进一步格式化成特定的字符串(例如,将短语用 ” 包裹并连接),则需要额外的后处理步骤。例如,如果需要将 [“on time”, “flight”] 转换为 “”on time”flight”,可以这样做:

const tokens = splitToWordsWithQuotes('"on time" "flight"');const formattedOutput = tokens.map(token => {    // 如果token包含空格,通常意味着它是一个短语,用双引号包裹    if (token.includes(' ')) {        return `"${token}"`; // 或者 `"${token}"` 如果需要字面量反斜杠    }    return token;}).join('');console.log('格式化后的输出:', formattedOutput); // 示例输出: "on time"flight

请注意,原始问题中期望的 “”ON TIME”FLIGHT” 格式可能包含额外的转义或特定含义,需要根据具体需求调整后处理逻辑。

总结

通过采用有限状态机(FSM)的方法,我们能够健壮且高效地将复杂的文本字符串拆分为独立的词语和带引号的短语。这种方法不仅解决了简单 split() 函数的局限性,还为处理更复杂的文本解析任务提供了可扩展的基础。理解FSM的原理并将其应用于字符串处理,是提升JavaScript编程技能的重要一步。

以上就是文本分词与带引号短语保留的JavaScript实现的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1518971.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月20日 12:01:55
下一篇 2025年12月20日 12:02:08

相关推荐

  • 前端数据属性搜索指南:实现精确匹配与模糊查询

    本文详细介绍了如何在前端开发中,特别是使用jQuery时,对HTML元素的data属性进行有效搜索。教程涵盖了两种主要方法:一是利用jQuery选择器实现data属性的精确匹配查找;二是引入第三方库Fuse.js,实现更灵活、支持部分匹配和容错的模糊搜索功能,并提供了详细的代码示例和实现步骤,帮助开…

    好文分享 2025年12月20日
    000
  • 如何实现一个单页应用(SPA)的核心路由与状态管理?

    单页应用通过前端路由与状态管理实现无缝视图切换与数据同步。前端路由利用 History API 动态更新视图,支持懒加载以提升性能;状态管理采用 Redux、Pinia 等工具统一数据流,确保组件间状态一致;路由与状态协同工作,使 URL 变化与应用数据联动,从而实现高效流畅的用户体验。 单页应用(…

    2025年12月20日
    000
  • 动态生成HTML元素的高效JavaScript事件绑定:事件委托机制详解

    当页面动态生成HTML元素并需要为其绑定JavaScript事件时,直接在每个元素中嵌入脚本会导致性能问题和代码冗余。本文将介绍如何利用事件委托(Event Delegation)机制,通过在父元素上设置单个事件监听器,高效且优雅地处理所有动态子元素的事件,避免页面刷新,并确保代码的可维护性和扩展性…

    2025年12月20日
    000
  • JavaScript中处理文件输入与DOM图片展示的完整指南

    本文详细指导如何在Web页面中接收用户上传的图片文件并将其动态展示到DOM元素中。文章深入探讨了JavaScript中常见的DOM操作错误(如方法名拼写、获取元素集合而非单个元素)以及浏览器针对本地文件路径的安全限制,并提供了使用FileReader API的专业解决方案,确保图片能够正确、安全地加…

    2025年12月20日
    000
  • jQuery与Fuse.js:实现HTML data 属性的精确与模糊搜索教程

    本教程详细介绍了如何使用jQuery对HTML元素的data属性进行精确和模糊搜索。针对精确匹配,我们将利用属性选择器;对于更复杂的模糊搜索需求,包括处理重音字符和部分匹配,我们将引入功能强大的第三方库Fuse.js,并提供详细的集成与使用示例,帮助开发者构建高效灵活的前端搜索功能。 在前端开发中,…

    2025年12月20日
    000
  • JavaScript的异步函数错误处理有哪些最佳实践?

    异步函数中需用try/catch捕获await的Promise错误,避免未处理拒绝;通过分类错误类型区分处理,补充上下文信息便于调试,并统一全局错误兜底机制。 JavaScript异步函数中的错误处理是确保程序健壮性的关键。由于异步操作的非阻塞性质,错误不会像同步代码那样自然冒泡到外层作用域,因此需…

    2025年12月20日
    000
  • 在JavaScript中如何处理异步编程的复杂性?

    JavaScript通过Promise和async/await解决回调地狱问题。Promise有pending、fulfilled、rejected三种状态,使用.then()和.catch()链式调用处理异步结果与错误;async/await基于Promise,使异步代码更像同步,提升可读性,并结…

    2025年12月20日
    000
  • 无需本地存储:将剪贴板图像作为文件上传至服务器的通用策略

    本文探讨了如何在不将剪贴板图像保存到本地文件系统的情况下,将其作为文件发送至服务器。核心策略是将位图数据转换为字节流,并通过HTTP multipart/form-data请求进行传输,确保高效且无痕的数据上传。教程将涵盖从客户端获取位图、数据编码、构建请求到服务器端处理的完整流程,并提供关键注意事…

    2025年12月20日
    000
  • JavaScript 循环处理数据时对象引用陷阱与解决方案

    本教程深入探讨了在 JavaScript 循环中处理对象数据时,因对象引用特性而导致只保存最后一条数据的常见问题。我们将详细解释该问题产生的根本原因,即在循环外部声明并反复修改同一对象实例,导致数组中所有元素都指向同一个内存地址。教程提供了清晰的示例代码,并展示了通过在每次循环迭代中创建新的对象实例…

    好文分享 2025年12月20日
    000
  • JavaScript的类静态字段与实例字段有何区别?

    静态字段属于类本身,通过类名访问,所有实例共享;实例字段属于每个实例,通过对象访问,每创建一个实例分配独立内存。 JavaScript中的类静态字段和实例字段主要区别在于它们所属的对象层级不同,影响着访问方式和使用场景。 静态字段属于类本身 静态字段通过 static 关键字定义,归属于类本身,而不…

    2025年12月20日
    000
  • JavaScript中的严格模式(Strict Mode)解决了哪些历史遗留问题?

    严格模式通过”use strict”限制危险行为,禁止意外创建全局变量、重复参数名、使用with语句,增强对象操作安全性,规范this指向,阻止八进制语法等,提升代码安全与可维护性。 JavaScript的严格模式通过在脚本或函数顶部添加 “use strict&…

    2025年12月20日
    000
  • JavaScript实现Datalist选项ID与Input数据属性的动态绑定

    本教程详细指导如何使用JavaScript动态获取HTML datalist 元素中选定 option 的 id 属性,并将其赋值给关联 input 元素的 data-set 自定义数据属性。通过监听 input 事件,确保用户在选择或输入时,input 字段的 data-set 和 value 属…

    2025年12月20日
    000
  • JavaScript剪刀石头布游戏:优化prompt输入处理与完善胜负判断逻辑

    本文旨在解决JavaScript剪刀石头布游戏中常见的两个问题:prompt输入处理不当导致无法正确识别空输入,以及游戏胜负判断逻辑不完整。我们将详细探讨prompt行为差异、提供健壮的输入验证方案,并重构游戏核心判断逻辑,确保所有对战情况均能正确判定结果,从而提升游戏的用户体验和代码的准确性。 在…

    2025年12月20日
    000
  • Underscore.js 链式调用:从嵌套数组中统计元素出现频率的教程

    本教程旨在指导如何使用 Underscore.js 从嵌套数组结构中高效统计元素的出现频率,例如从多支球队的球员名单中统计每个球员名字的出现次数。文章将重点介绍 _.countBy() 方法的简洁性与效率,并提供两种实现方案:结合原生 flatMap() 或纯 Underscore 链式调用 _.m…

    2025年12月20日
    000
  • 获取Datalist选项ID并将其赋值给Input的Data属性

    本教程详细阐述了如何通过JavaScript监听input事件,捕获用户从HTML datalist中选择的选项的id属性,并将其动态赋值给关联input元素的data-set自定义属性。文章提供了清晰的步骤、示例代码和关键API解释,帮助开发者实现datalist选择与隐藏数据关联的常见需求,确保…

    2025年12月20日
    000
  • JavaScript循环中对象引用陷阱:解决数据覆盖与只记录最后一个的问题

    本文深入探讨了JavaScript循环处理数据时,由于对象引用特性导致的常见问题:在循环中修改并添加到数组的对象,最终可能只保留最后一个数据或所有数据相同。文章将详细解释这一现象的原理,并通过代码示例展示如何通过在每次循环迭代中创建新对象来有效解决此问题,确保数据正确独立地存储。 循环中数据覆盖现象…

    2025年12月20日
    000
  • 实现HTML范围滑块居中值显示:CSS与JavaScript的结合应用

    本文详细介绍了如何利用CSS的::after伪元素、data-*属性和JavaScript动态更新,在HTML范围滑块(range slider)的中心位置显示当前值。通过分离结构、样式和行为,该教程提供了一种优雅且可维护的解决方案,避免了传统方法如标签的局限性,并提升了用户体验,适用于需要自定义滑…

    2025年12月20日
    000
  • 在React JS项目中通过CDN引入React-Select的完整指南

    在React JS应用中通过CDN引入React-Select时遇到的常见“未定义”错误。核心问题在于缺少必要的依赖库,如React、ReactDOM和Emotion。教程将提供完整的CDN链接列表及正确的加载顺序,并通过一个完整的HTML示例,指导开发者如何成功在浏览器环境中集成并使用React-…

    2025年12月20日
    000
  • 如何使用 Underscore.js 处理嵌套数组并统计元素出现次数

    本文旨在探讨如何利用 Underscore.js 高效地处理嵌套数组数据,并统计其中特定元素的出现频率。我们将介绍使用 _.countBy() 这一 Underscore.js 内置方法的最佳实践,并通过链式调用 _.map() 和 _.flatten() 来准备数据。同时,我们也会深入分析 _.r…

    2025年12月20日
    000
  • 如何实现一个JavaScript的颜色选择器?

    答案是使用原生input[type=”color”]可快速实现基础颜色选择器,通过监听change事件获取十六进制颜色值;若需自定义UI,则需结合HTML、CSS与JavaScript构建色相、饱和度、亮度等调节区域,利用canvas或CSS渐变绘制调色板,通过鼠标交互获取坐…

    2025年12月20日
    000

发表回复

登录后才能评论
关注微信