← 中文资源

简体中文版本

字符串游程编码与解码

把连续重复字符编码为数量加字符的紧凑形式,并在解码时严格拒绝残缺或非法的游程记号。

阅读英文原文

功能说明

runLengthEncode 把连续重复字符转换为数量加字符的记号,例如 aaabb 变成 3a2b;runLengthDecode 执行逆操作并校验输入格式。实现按 Unicode 码点而不是 UTF-16 码元遍历,因此 emoji 与代理对不会被拆开。适用于简单压缩、棋盘或图像行数据、日志去重以及传输前的体积优化。

关键代码

编码阶段用展开运算符把字符串转成码点数组,外层循环定位每段起点,内层统计连续相同字符的数量,再拼接数量与字符。解码阶段用带 u 标志的正则逐个匹配数字与任意码点,matchAll 提供位置信息,代码据此要求记号首尾相接,任何多余字符或缺失数字都会抛错。数量必须是正的安全整数,避免 repeat 造成异常内存分配。

使用注意

该格式只适合重复率高的数据,随机字符串编码后可能比原文更长,使用前应比较体积。数字与字符直接相连,若原文本身包含数字不会混淆,但解码器只接受严格交替的记号。超长重复串可能触发 repeat 的内存上限,处理不可信输入时应先限制解码后的最大长度。编码结果不包含校验和,损坏检测仅限格式层面。

验证方式

测试空字符串、单字符、全相同字符、交替字符以及包含 emoji 的字符串,断言编码后再解码等于原文。构造 3a2b、12z 等合法输入验证数量解析,测试缺少数字、数量为零、负号、尾随垃圾与空记号等非法输入。再对随机字符串做属性测试,确认往返一致,并验证非字符串参数会抛出类型错误。

代码示例

export function runLengthEncode(input) {
  if (typeof input !== "string") throw new TypeError("input must be a string");
  const characters = [...input];
  let encoded = "";
  for (let index = 0; index < characters.length; ) {
    let run = 1;
    while (index + run < characters.length && characters[index + run] === characters[index]) {
      run += 1;
    }
    encoded += run + characters[index];
    index += run;
  }
  return encoded;
}

export function runLengthDecode(encoded) {
  if (typeof encoded !== "string") throw new TypeError("encoded must be a string");
  const token = /(\d+)([\s\S])/gu;
  let decoded = "";
  let consumed = 0;
  for (const match of encoded.matchAll(token)) {
    if (match.index !== consumed) throw new SyntaxError("invalid run-length token");
    const count = Number(match[1]);
    if (!Number.isSafeInteger(count) || count <= 0) {
      throw new RangeError("run length must be a positive safe integer");
    }
    decoded += match[2].repeat(count);
    consumed = match.index + match[0].length;
  }
  if (consumed !== encoded.length) throw new SyntaxError("invalid run-length token");
  return decoded;
}