RxJS 实现摩斯密码(内附脑图)

参加 2018 ngChina 开发者大会,特别喜欢 Michael Hladky 奥地利帅哥的 RxJS 分享,现在拿出来好好学习工作坊的内容(工作坊Demo地址),结合这个示例,做了一个改进版本,实现更简洁,逻辑更直观。

一、摩斯密码是什么?

了解者可跳过次章节

摩斯密码(Morse),是一种时通时断的信号代码,这种信号代码通过不同的排列组合来表达不同的英文字母、数字和标点符号等。
地球人都知道的 SOS 求救信号,就是 Morse,三短(S) 三长(O) 三短(S)。

信号对应表如下:

image.png

二、业务逻辑分析

分析关键步骤,很巧,和把大象装进冰箱里同样都只需要三步耶:

名列前茅步,识别点信号,短为 “滴” 长为“嗒”。

第二步,根据 “长间隔” 来切片分组。

第三步,分组数据根据对应表转化出最终结果。

三、撸代码,优化后版本(完整在线示例)

开始前要做好热身活动:

Morse 的最小单元,”.” 代表嘀,”-” 代表嗒,点击事件用 Down 代表 mousedown,Up 代表 mouseup。
200ms 间隔用来区别嘀嗒,1s 间隔用来区分一个 Morse 单元组的结束。

 // 点信号 = Down - Up = 间隔 < 200ms ?"." : "-"; 
// Down <200ms Up >1s = "." = E
// Down <200ms Up <1s Down >200ms Up >1s = ".", "-" = A

// 直接使用 fromEvent 操作符,来生成点击操作的流,然后用 map 操作符转化成时间戳,
// takeUntil 用来控制流的结束,避免重复订阅。

const clickBegin$ = fromEvent(this.sendButtonElementRef.nativeElement, 'mousedown')
  .pipe(
    takeUntil(this.onDestroy$),
    map(n => Date.now())
  )
const clickEnd$ = fromEvent(this.sendButtonElementRef.nativeElement, 'mouseup')
  .pipe(
    takeUntil(this.onDestroy$),
    map(n => Date.now())
  ) 
名列前茅步,识别点信号为 “滴” “嗒”

前面代码已经拿到点击事件的流,并且用 “map” 操作符,把数据转化为当前的时间戳。

下面开始计算 Down & Up 之间的间隔时间,思考,合并两个流的的操作符有哪些呢?

forkJoin、concat ?
需要两个流 complate 状态后才返回数据,不适应数据持续输出的场景。

merge ?
Down & Up 的时间戳不会同时获得,还需要处理存储的问题,不完全适应场景。

combineLatest ?
满足数据持续输出,满足同时获得,哎哟,还不错。
但是这个操作符的特点是,会缓存上一次的值,所以第二次 Down 也会获得到数据,Up – Down 也就会为负值,取绝对值后可以用来判断是否 >1s,来区分一个 Morse 单元组的结束。

zip ?
哎呀哈,这个更合适呢,盘它!
单词选的很到位,这个操作符功能可以理解为像拉链一样,确保获得数据每一次都是一个纯净的 Down & Up。
但是需要注意 zip 会自动缓存数据,例如 zip(A$, B$),A$收到的数据一直比B$多太多,有内存溢出风险,就像拉错位的拉链,很蓝瘦。

 // zip的实现
zip(clickBegin$, clickEnd$)
    .pipe(
    // 计算 Down - Up 间隔时间
    map(this.toTimeDiff),
    // 根据间隔时间,转化为嘀嗒替代字符 "." "-"
    map(this.msToMorseCharacter)
    )
    .subscribe(result => {
      // 发送到主信号流
      morseSignal$.next(result);
    }); 
第二步,根据 “长间隔” 来切片分组

分组的操作符有哪些?

partition?
根据函数拆成两个流。

groupBy?
根据函数拆成 n 个流。

window?
根据流拆成 n 个流。以上各位都打扰了,我还要自己处理数据缓存,再见。

buffer?
哇,初恋般的感觉,用流控制来做切片数据成数组,拿到数组只需要 join 一下就好,就可以去去匹配对应表了,好棒!
“长间隔”的切片流,怎么获得呢?拿出法宝 debounceTime(1000) ,当点击的 Down Up 周期完成后,间隔 1s 就认为是一个Morse 单元组的结束。
然后又遇到了问题,怎么判断一个点击周期呢?不用单纯用 Up ,因为下一个 Down Up 周期可能会超出 1s,就会导致切片时机错误。所以模拟了点击持续的流 clickKeeping$,用 switchMap 替换为新的流且不影响原来的流,timer 产生一个小于 1s 间隔的持续流信号,用 takeUntil 在 Up 事件流 clickEnd$ 后把整个流结束。

 // 点击持续状态流
const clickKeeping$ = clickBegin$
    .pipe(
        // 替换为新的流,不影响原来的流
        switchMap(() => {
            // 定时在持续发送数据,维持点击中状态
            return timer(0, morseTimeRanges.lessThenlongBreak).pipe(
                // 直到 Up 后结束点击状态
                takeUntil(clickEnd$)
            );
        })
    )

// “长间隔”的切片流
const morseBreak$ = clickKeeping$.pipe(
    debounceTime(morseTimeRanges.longBreak)
);

// 获得 Morse 单元组
morseSignal$
    .pipe(
        // 切片分组主信号流
        buffer(morseBreak$) // 转化为,例如 ['.', '.', '.']
    ) 
第三步,分组数据根据对应表转化出最终结果

join(”) Morse 单元组去匹配对应表,很简单不用说。

错误发生在 switchMap 中,分支流报错,但是主流不会收到影响,然后用 catchError 捕捉错误。

 // Morse 单元组去匹配对应表
private translateSymbolToLetter = morseArray => {
    const morseCharacters = morseArray.join('');
    const find = morseTranslations.find(n => n.symbol === morseCharacters)
    // 这里 find 可能为 undefined 导致报错,但是错误会被 catchError 捕捉
    return find.char;
}

// 转化+错误处理,最终完成
morseSignal$
    .pipe(
        buffer(morseBreak$),
        switchMap(n => {
            return of(n).pipe(
                // 只为了 Demo 演示中的展示用
                tap(n => this.lastMorseGroupCharacters = n.join(' ')),
                // 转化成对应表中字符
                map(this.translateSymbolToLetter),
                // 捕捉错误
                catchError(n => {
                    return of(morseCharacters.errorString);
                })
            )
        })
    )
    .subscribe(result => {
        // 输出最终转化结果
        this.morseLog.push(result);
        console.log('结果:', result)
    }); 

四、解读 Michael Hladky 大神的示例

整体上,把 “嘀嗒” “短间隔” “长间隔” 都转化成替代符,过滤无用的替代符,然后 filter “长间隔” 替代符的流,来做 buffer 切片数据。其他还有因为使用 combineLatest 操作符导致的不同。

// 识别 “嘀” “嗒”
const morseCharFromEvents$ = observableCombineLatest(this.startEvents$, this.s较好Events$)
    .pipe(
        // 计算 mousedown mouseup 时间间隔
        map(this.toTimeDiff),
        // 转化成标识符
        map(this.msToMorseChar),
        // 过滤 Morse 单元组中的 “短间隔“ 标识符
        filter(this.isCharNoShortBreak as any)
    );

// 主信号流
this.morseChar$ = observableMerge(morseCharFromEvents$, this.injectMorseChar$)

// 识别 “长间隔“ 标识符,来作为切片流
const longBreaks$ = this.morseChar$
    .pipe(filter(this.isCharLongBreak as any));

// 切片成 Morse 单元组
this.morseSymbol$ = this.morseChar$
    .pipe(
        buffer(longBreaks$),
        map(this.charArrayToSymbol),
        filter(n => (n !== '') as any)
    )

// 错误处理 + 标识符对应表转化
this.morseLetter$ = this.morseSymbol$
    .pipe(
        switchMap(n => observableOf(n).pipe(this.saveTranslate('ERROR')))
    );

// Up 后补4个 “长间隔“ 标识符,用来做 Morse 单元组的结束
const breakEmitter$ = observableTimer(this.msLongBreak, this.msLongBreak)
    .pipe(
        mapTo(this.mC.longBreak),
        take(4)
    );
this.s较好EventsSubject
    .pipe(
        switchMapTo(
          breakEmitter$.pipe(takeUntil(this.startEventsSubject))
        )
    )
    .subscribe(n => this.injectMorseChar(n)); 

总结

下图是读完《深入浅出RxJS》后的学习笔记,标注了一些操作符的快速记忆特点,方便使用的适合查阅。

image.png

文章标题:RxJS 实现摩斯密码(内附脑图),发布者:刘佳,转载请注明出处:https://worktile.com/kb/p/6537

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
刘佳刘佳认证作者
上一篇 2022年3月20日 上午1:31
下一篇 2022年3月20日 上午2:23

相关推荐

  • 为什么vscode打包出来空白

    Visual Studio Code (VSCode) 打包出来空白可能是由于路径配置错误、资源未正确加载、构建环境不当、扩展问题或脚本错误等原因。核心原因包括:错误的构建脚本配置、缺失的依赖文件、不兼容的插件或扩展、缺陷的代码逻辑。对于构建脚本配置错误,确保脚本正确引用了所有需要的文件和资源,以及…

    2024年4月3日
    7100
  • 企业管理软件研发

    企业管理软件研发需关注用户需求满足与技术创新相结合、保持用户界面友好且直观、确保数据安全与合规性、适应性与可扩展性、集成现有系统与工具。接下来将对用户需求满足与技术创新相结合进行详细阐述。 用户需求满足与技术创新相结合是企业管理软件研发的关键。组织寻求软件解决方案以提高操作效率、降低成本并增强决策能…

    2024年1月10日
    20900
  • oa流程是什么意思

    OA流程代表办公自动化系统(Office Automation System)中的工作流程,它在企业或组织中提供了文档管理、审批流转、信息传递和工作协调的电子化解决方案。1、信息化处理流程;2、效率提升;3、协同工作便捷化;4、成本与时间节省。尤其在现代企业环境下,随着信息化水平的提高,有效利用OA…

    2024年1月11日
    52200
  • 低代码平台如何创新传统编码?

    低代码开发平台诞生的初衷,是加快应用程序开发进程,而在现在的软件开发领域,低代码往往被用作响应“小时级”需求的优先选项,这也印证了低代码的发展变化始终都遵循一个逻辑——以最少的编码快速生成企业级应用解决方案。

    2023年7月17日
    35300
  • 项目管理平台的核心价值是什么

    项目管理平台的核心价值主要体现在以下几个方面:1、提高协同效率;2、跟踪项目进度;3、控制项目成本;4、风险管理;5、资源调度;6、提供决策支持。一个优异的项目管理平台能够帮助团队更高效地管理项目,确保项目的顺利进行。 1、提高协同效率 项目管理平台的一个重要功能是提高团队的协同效率。通过共享文档、…

    2023年7月10日
    53400
  • 如何使用社交分享增加网站流量

    使用社交分享增加网站流量的建议:1、确定目标受众;2、创建高质量的内容;3、确定合适的社交媒体平台;4、优化社交媒体分享按钮;5、使用社交媒体广告;6、与其他社交媒体用户互动;7、跟踪和分析社交分享数据;8、定期发布内容;9、提供激励措施;10、培养忠实粉丝。

    2023年11月11日
    27800
  • 项目前期总监如何管理工作

    项目前期总监在管理工作时应致力于确保项目成功铺设基础。首要职责包括:制定详尽的项目计划、明确项目范围、确保资源的有效配置、与团队和利益相关者沟通、监控和调整项目进度、确保质量控制。在这些高层次目标中,制定详尽的项目计划是至关重要的一步,它包括确定项目目标、任务、时间表、成本预估和风险分析。 一、项目…

    2024年4月11日
    3700
  • devops是什么职业

    DevOps是一种职业文化与实践,旨在加强开发(Dev)与运维(Ops)的合作和沟通,通过自动化流程来提高软件交付的速度和质量。它侧重于持续集成、交付和部署,并鼓励更紧密的业务与技术目标对齐。在此文化中,某个专业角色扮演者深谙软件开发、测试、部署到运行维护的全链路,是缩短系统开发生命周期、确保高质量…

    2024年3月26日
    5800
  • 如何落实项目的品质管理

    项目的品质管理是确保项目达到其既定目标和表现要求的关键环节。为有效落实项目品质管理,项目管理人员需要遵循一系列明确的步骤:制定品质管理计划、执行品质保证、进行品质控制、以及持续的品质改进。这些步骤不仅确保项目成果符合质量要求,而且有助于提高团队的工作效率和项目的整体成功率。特别是制定品质管理计划,这…

    2024年4月10日
    5100
  • 产品线路图软件有哪些

    产品线路图软件有以下几款:1、Airfocus;2、Aha!;3、Craft.io;4、Monday.com;5、Productboard;6、Roadmunk;7、ZenHub Roadmaps。产品路线图软件可以帮助软件产品经理完成核心的规划任务,并向项目团队成员和相关人员通报目标和状态。 1、…

    2022年11月30日
    64200

发表回复

登录后才能评论
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部