编写一个以文本为输入的程序,并生成一个再现该文本的程序

Write a program that takes text as input and produces a program that reproduces that text

本文关键字:文本 程序 一个 一个以 输入      更新时间:2023-10-16

最近我遇到了一个很好的问题,它变得既简单易懂,又难以找到任何解决方法。问题是:

编写一个程序,从输入中读取文本并打印其他文本程序输出。如果我们编译并运行打印的程序,它必须输出原始文本。

输入文本应该相当大(超过10000个字符)。

唯一(也是非常严格)的要求是,存档(即打印的程序)的大小必须严格小于原始文本的大小。这使得像这样显而易见的解决方案变得不可能

std::string s;
/* read the text into s */
std::cout << "#include<iostream> int main () { std::cout<<"" << s << ""; }";

我认为这里要使用一些归档技术。

不幸的是,这样的程序并不存在。

要知道为什么会这样,我们需要做一些数学运算。首先,让我们统计有多少长度为n的二进制字符串。每个比特可以是0或1,这为每个比特提供了两个选择之一。由于每个比特和n个比特有两个选择,因此总共有2个长度为n的二进制串。

现在,让我们假设我们想要构建一个压缩算法,它总是将长度为n的比特串压缩为长度小于n的比特字符串。为了使其工作,我们需要计算出有多少不同的长度小于n。好吧,这是由长度为0的比特串的数量,加上长度为1的比特串数量,加上将长度为2的比特串数目,等等给出的,一直到n-1。这个总数是

20+21+22+…+2n-1

通过一点数学运算,我们可以得到这个数字等于2n-1。换句话说,长度小于n的比特串的总数比长度为n的比特字符串的数量小一个。

但这是个问题。为了让我们有一个无损压缩算法,它总是将长度为n的字符串映射到长度最多为n-1的字符串,我们必须有某种方式将长度为n的每个比特串与某个较短的比特串相关联,这样就不会有两个长度为m的比特串与相同的较短比特流相关联。通过这种方式,我们可以通过将字符串映射到关联的较短字符串来压缩字符串,也可以通过反转映射来解压缩字符串。长度为n的两个比特串都不能映射到同一个较短的字符串,这一限制使得这种无损-如果两个长度为n的比特串映射到相同的较短的比特串,那么当需要解压缩字符串时,就无法知道我们压缩了两个原始比特串中的哪一个。

这就是我们遇到问题的地方。由于存在长度为n的2个不同的比特串,并且只有2个较短的比特串n-1个,因此我们不可能在不向同一较短串分配至少两个长度为n比特串的情况下将长度为n个的每个比特串与某个较短比特串配对。这意味着,无论我们多么努力,无论我们有多聪明,无论我们的压缩算法有多有创意,都有一个严格的数学限制,那就是我们不能总是让文本变短。

那么,这与你最初的问题是如何对应的呢?好吧,如果我们得到一个长度至少为10000的文本字符串,并且需要输出一个较短的程序来打印它,那么我们必须有某种方法将长度为10000的2个10000字符串中的每一个映射到长度小于10000的2<10000>-1个字符串上。该映射还有一些其他属性,即我们总是必须生成一个有效的程序,但这在这里无关紧要——根本没有足够的短字符串可供使用。因此,你想解决的问题是不可能的。

也就是说,我们可能能够得到一个程序,它可以将长度为10000的字符串中除一个外的所有字符串压缩为较短的字符串。事实上,我们可能会找到一种压缩算法来做到这一点,这意味着在概率为1-210000的情况下,任何长度为10000的字符串都可以被压缩。这是一个很高的概率,如果我们在宇宙的一生中一直在挑选字符串,我们几乎肯定永远不会猜到"一个坏字符串"。


为了进一步阅读,信息论中有一个概念叫做Kolmogorov复杂性,它是产生给定字符串所需的最小程序的长度。一些字符串很容易被压缩(例如,ababababababab),而另一些则不然(例如,sdkjhdbvljkhwqe2305089)。存在被称为不可压缩字符串的字符串,对于这些字符串,字符串不可能被压缩到任何更小的空间中。这意味着任何打印该字符串的程序都必须至少与给定字符串一样长。为了更好地介绍Kolmogorov复杂性,你可能想看看Michael Sipser的"计算理论导论,第二版"第6章,其中对一些较酷的结果进行了极好的概述。要想更严谨、更深入地研究,可以考虑阅读第14章"信息理论的要素"。

希望这能有所帮助!

如果我们谈论的是ASCII文本。。。

我认为这实际上可以完成,并且我认为文本将大于10000个字符的限制是有原因的(给你编码空间)。

这里的人说字符串不能压缩,但它可以。

为什么?

要求:输出原始文本

文本不是数据。当您读取输入文本时,您读取的是ASCII字符(字节)。其中包含可打印和不可打印的值。

举个例子:

ASCII values    characters
0x00 .. 0x08    NUL, (other control codes)                                  
0x09 .. 0x0D    (white-space control codes: 't','f','v','n','r')
0x0E .. 0x1F    (other control codes)
... rest of printable characters

由于必须将文本打印为输出,因此您对范围(0x00-0x08,0x0E-0x1F)不感兴趣。您可以通过使用不同的存储和检索机制(二进制模式)来压缩输入字节,因为您不必返回原始数据,而是返回原始文本。您可以重新计算存储值的含义,并将它们重新调整为要打印的字节。实际上,您将只释放非文本数据的数据,因此这些数据不可打印或输入。如果WinZip这样做,那将是一个巨大的失败,但对于您所声明的要求来说,这根本无关紧要。

由于要求文本为10000个字符,您可以保存255个字符中的26个,因此如果您的包装没有任何损失,您可以有效地节省大约10%的空间,这意味着如果您可以用1000个字符(10000个字符的10%)编码"解压缩",您就可以实现这一点。您必须将10个字节的组视为11个字符,然后根据229的范围,通过某种外推方法外推te 11。如果能够做到这一点,那么问题就可以解决。

尽管如此,它需要聪明的思维和编码技能,才能在1千字节内真正做到这一点。

当然,这只是一个概念性的答案,而不是一个功能性的答案。我不知道我是否能做到这一点。

但我有一种冲动,想为此付出2美分,因为每个人都觉得这是不可能的,因为我对此非常确信

问题中真正的问题是理解问题和需求。

您所描述的基本上是一个用于创建自解压zip档案的程序,只是一个小的区别,即常规的自解压zip存档将原始数据写入文件而不是stdout。如果你想自己制作这样一个程序,有很多压缩算法的实现,或者你可以自己实现例如DEFLATE(gzip使用的算法)。"外部"程序必须压缩输入数据并输出用于解压缩的代码,并将压缩的数据嵌入该代码中。

伪码:

string originalData;
cin >> originalData;
char * compressedData = compress(originalData);
cout << "#include<...> string decompress(char * compressedData) { ... }" << endl;
cout << "int main() { char compressedData[] = {";
(output the int values of the elements of the compressedData array)
cout << "}; cout << decompress(compressedData) << endl; return 0; }" << endl;
  1. 假设"字符"意味着"字节",并且假设输入文本可能包含至少与编程语言一样多的有效字符,则不可能对所有输入执行此操作,因为正如templatepedef所解释的,对于任何给定长度的输入文本,所有"严格较小"的程序本身都可能是长度较小的输入,这意味着可能的输入比可能的输出更多。(通过使用以"如果这是1,以下只是未编码的输入,因为它无法进一步压缩"位开头的编码方案,可以安排输出最多比输入长一位)

  2. 假设它足以对大多数输入(例如,主要由ASCII字符组成的输入,而不是所有可能的字节值)执行此操作,那么答案很容易存在:使用gzip。这就是它的长处。没有什么比这更好的了。您可以创建自解压档案,也可以将gzip格式视为"语言"输出。在某些情况下,使用完整的编程语言或可执行文件作为输出可能会更高效,但通常情况下,通过使用为该问题设计的格式(即gzip)来减少开销会更高效。

它被称为产生自提取归档的文件归档器。