摩斯电码:一张表、两处歧义、三个实现细节
摩斯电码本身很简单,麻烦都在细节里:点划的空格归属、全角符号的归一化,以及「一个单位」到底有多长。
摩斯电码是那种「看一眼就懂、写起来全是坑」的东西。表只有一张,歧义有两处,实现时还要想清楚三个细节。
表:不要只收字母
只列 26 个字母的转换器只能做玩具。数字、常用标点各占一块,此外还有呼号(prosign)—— 它们不是字母的组合,而是有独立含义的连写码,比如 ...---... 是国际求救,-...- 是换段。呼号没有字母间隔,所以解码时不能按普通单词拆。
数据表放在纯逻辑里,界面上那张对照表直接由它渲染,避免两处维护同一份事实。
歧义一:空格归谁
编码时,字母之间一个空格、单词之间一个斜杠:
encode('HI ALL').code; // '.... .. / .- .-.. .-..'
解码时反过来,斜杠切单词、空格切字母。但用户粘贴进来的电码往往只有空格没有斜杠,这时它就只是一个单词里的多个字母 —— 时长计算会按「字母间隔 3 个单位」算,而不是单词间隔 7 个。这不是 bug,是必须写进文档的口径。
歧义二:什么算「点」
从网页、文档或聊天窗口复制来的电码经常混着全角与各种长得像点划的符号。与其在解析时报错,不如先归一化:
normalizeCode('·−·−'); // '.-.-'
normalizeCode('-_-'); // '---'
点、句号、圆点、间隔号一律变成 .;各种短横、长横、下划线变成 -;竖线当字母分隔符。这一步做完,后面的解析就只有两个字符要认。
细节一:时长是有标准的
「一个单位」= 一个点。划是三个单位,字母内符号间隔一个单位,字母之间三个单位,单词之间七个单位。所以 S 是 5 个单位,... --- ... 是 27 个单位。界面上给出总时长,按 20 WPM(一个单位 60 毫秒)换算。
这里我第一版写错过一次:只累加了符号本身的单位,忘了符号之间的间隔,S 算出来是 3。自检里加一条 timingUnits('...') === 5 就再也错不了了。
细节二:播放与时长要同源
既然要能听,播放的脉冲序列和时长计算必须来自同一份定义,否则「显示 0.3 秒、实际响 0.36 秒」。所以 pulses() 生成 { on, units } 的数组,timingUnits() 算总和,自检里断言两者相等 —— 这条断言在两个函数之间钉了一颗钉子。
音频用 WebAudio 现场合成:620 Hz 正弦、每个脉冲起止各加一小段包络,避免爆音。浏览器不给音频权限就静默失败,工具本身仍然可用。
细节三:认不出的东西要说出来
编码遇到表外的字符(比如汉字),不要静默丢掉。返回一个 unknown 数组,界面上标出来:用户至少知道哪几个字没有转成电码。解码遇到不合法的码字同理,用 ? 占位并列出原文。
静默兜底是工具类代码里最常见、也最伤人的错误。
收尾
摩斯电码的实现难度不在算法,而在口径:空格算字母还是单词、哪些字符算点、一个单位多长。把这三件事写清楚并钉进自检,剩下的就只是渲染了。

评论
…