【问题标题】:Any way to reliably compress a short string?有什么方法可以可靠地压缩短字符串?
【发布时间】:2012-11-20 20:54:56
【问题描述】:

我有一个长度正好为 53 个字符的字符串,其中包含一组有限的可能字符。

[A-Za-z0-9\.\-~_+]{53}

我需要在不丢失信息并使用相同字符集的情况下将其减少到 50 的长度。

我认为应该可以将大多数字符串压缩到 50 长度,但是否可以压缩所有可能长度为 53 的字符串?我们知道,在最坏的情况下,可能集合中的 14 个字符将未被使用。我们可以使用这些信息吗?

感谢阅读。

【问题讨论】:

  • "我们知道在最坏的情况下,可能的集合中的 14 个字符将不会被使用。"你能详细说明一下吗?
  • 我相信这里需要 encoding,而不是 compression
  • @JohnKugelman 53 个字符串,每个字符不同。 67 个可能的字符(52 个字母、10 个数字、5 个符号)。 67-53 = 14 未使用。
  • @pst 谢谢,我已将其添加为标签。

标签: javascript string encoding compression query-string


【解决方案1】:

如果,如您所说,您的输出字符串必须使用与输入字符串相同的字符集,并且如果您对输入字符串的要求不了解任何特殊情况,那么不,不可能压缩每个可能的 53 个字符的字符串,最多 50 个字符。这是pigeonhole principle的简单应用。

  • 您的输入字符串可以表示为 base67 中的 53 位数字,即从 0 到 67 的整数53 - 1 ≅ 6*1096.
  • 您希望将这些数字映射为 0 到 67 之间的整数50 - 1 ≅ 2*1091
  • 因此,根据鸽巢原理,您可以保证 673 = 300,763 个不同的输入将映射到每个可能的输出 - 这意味着,当您去解压缩时,您无法知道您应该映射回这 300,763 个原件中的哪一个。

要完成这项工作,您必须更改您的要求。您可以使用更大的字符集对输出进行编码(如果每个字符都有 87 个可能的值,而不是输入中的 67 个,则可以将其减少到 50 个字符)。或者您可以识别输入中的冗余——也许第一个字符只能是“3”或“5”,第 19 和第 20 是状态缩写,只能有 62 个不同的可能值,诸如此类。

如果您不能做任何这些事情,您将不得不使用压缩算法,例如 Huffman 编码,并接受这样一个事实,即某些字符串可以压缩(并且会变短)而其他字符串则不能(并且会加长)。

【讨论】:

  • 我喜欢它的发展方向 (+1) 但这并没有直接解决问题的一部分:If 14 个未使用的字符被识别然后输入空间只有53^53。现在,识别和使用此类信息是另一个问题,但是.. 它可以到达任何地方吗?如果没有,为什么不呢?
  • 不,输入空间还是67^53。 “14 个未使用的输入字符”无关紧要。这就像说“如果我有一个 3 位数字,那么至少有 7 个可能的数字未使用”。好吧,但谁在乎呢?这不会以某种方式将10^3 组合更改为3^3
  • 如果识别出未使用的字符 [已知] - 假设我知道,也许是因为我在有人键入代码时回头看了看,只有第一行(1 ,2,3) 被使用了..这会改变问题吗?
  • @pst,绝对的。这不是“未使用可能集中的 14 个字符”部分的内容,但如果您确实知道 3 位数字仅由三个可能的数字组成,那么它就变成了 3 - 以 3 为底的数字,而不是以 10 为底的数字,因此它将是 3^3
【解决方案2】:

你所问的在最一般的情况下是不可能的,这可以很简单地证明。

假设可以在同一个集合中将任意 53 个字符的字符串编码为 50 个字符。这样做,然后将三个随机字符添加到编码字符串。然后你有另一个任意的 53 个字符的字符串。你如何压缩它?

因此,不能保证您想要的内容适用于任何可能的数据。但是,您的所有真实数据可能具有足够低的熵,您可以设计一个可行的方案。

在这种情况下,您可能想要做一些霍夫曼编码的变体,它基本上为您集中的字符分配可变位长度编码,对最常用的字符使用最短的编码。您可以分析所有数据以得出一组编码。在霍夫曼编码之后,您的字符串将是一个(希望更短的)比特流,您将其编码为每个字符 6 位的字符集。对于您的所有真实数据,它可能足够短。

像 Smaz(在另一个答案中引用)这样的基于库的编码也可以工作。同样,无法保证它适用于所有可能的数据。

【讨论】:

    【解决方案3】:

    一个字节(字符)可以编码 256 个值(0-255),但您的有效字符集仅使用 67 个值,可以用 7 位表示(唉,6 位只能得到 64 个)并且没有任何字符使用字节的高位。

    鉴于此,您可以丢弃高位并仅存储 7 位,将下一个字符的初始位运行到第一个字符的“备用”空间中。这将只需要 47 个字节的空间来存储。 (53 x 7 = 371 位,371 / 8 = 46.4 == 47)

    这并不是真正的压缩,而是编码的变化。

    例如“ABC”是0x41 0x42 0x43

         0x41        0x42        0x43  // hex values
    0100 0001   0100 0010   0100 0011  // binary
     100 0001    100 0010    100 0011  // drop high bit
    // run it all together
    100000110000101000011
    // split as 8 bits (and pad to 8)
    10000011   00001010   00011[000]
        0x83       0x0A        0x18
    

    例如,这 3 个字符不会节省任何空间,但您的 53 个字符始终会显示为 47 个,保证。

    但是,请注意,如果这对您很重要,输出将不会使用您的原始字符集。

    流程变成:

    original-text --> encode --> store output-text (in database?)
    retrieve --> decode --> original-text restored
    

    【讨论】:

    • @wared - 我的意思是,如果您查看我的编码方法的输出,它看起来与输入完全不同 - “abc”可能变成“•=” - 因为我已经转移了每个向左一点。它无损的,所以解码总是会得到正确的原始字符串。在超过 7 个字符输入之前,它不会节省任何空间。
    • @wared 是的,可以恢复原始字符串(无损)。编码取8位,降高,左移,追加下一个重新编码的8位;解码您获取流的前 7 位,右移和零填充 1 位,取回原始的 8 位。由于您实际上是在读取字节流,因此您需要保存低位以用作下一个序列的第一位。请注意,所有这些都有效,因为原始输入(问题)保证高位始终为零。
    【解决方案4】:

    如果我没记错的话,Huffman coding 将是最紧凑的数据存储方式。太久没用它快速写算法了,不过大体思路覆盖了here,不过如果我没记错的话你的做法是:

    1. 获取每个使用的字符的计数
    2. 根据它们发生的频率对其进行优先级排序
    3. 根据优先级构建树
    4. 通过遍历树得到每个字符的压缩位表示(从根开始,left = 0 right = 1)
    5. 用树中的位替换每个字符

    【讨论】:

    • 我认为这不适用于每个字符不同的情况。据我了解,第一次出现的字符需要正常编码,然后添加到内部树结构中。
    • 这是真的,您确实需要编码转换为的前导表示,因此它可能不适用于像您使用的字符串一样短的字符串。
    • 检查this问题。
    【解决方案5】:

    Smaz 是一个简单的压缩库,适用于压缩非常短的字符串。

    【讨论】:

    • 一旦再次使用该字符集对结果进行编码,您能否确定该字符集中每个可能长度为 53 的字符串会压缩 3 个字符?
    • 查看示例,这可能不适用于此输入,因为 smaz 只能很好地压缩自然语言,因为它使用字典而不是通用算法。
    • Smaz 对我来说也很有希望,想在 Java 项目中使用 JSmaz。遗憾的是,目前仅支持 ASCII 输入。
    猜你喜欢
    • 1970-01-01
    • 2016-02-04
    • 2023-02-06
    • 2019-10-04
    • 2011-10-19
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多