文本压缩和解压缩-“再也没有”。


38

随着最近有关在代码高尔夫中使用压缩工具的讨论,我认为编写自己的文本压缩器和解压缩器将是一个不错的挑战。

挑战:

编写两个程序:一个程序将ASCII文本压缩为一个字节序列,另一个程序将其解压缩。程序不必使用相同的语言。

第一个程序应读取一段ASCII文本(从文件或标准输入,或使用该语言最自然的任何机制),并输出其压缩版本。(压缩的输出可以包含任意字节,也可以是任意字节;它不必可读。)第二个程序应读取第一个程序的输出并重新创建原始输入文本。

得分:

解决方案的分数将是以下三个计数的总和

  1. 压缩程序的长度(以字符为单位)。
  2. 输出的长度给出下面的测试输入,以字节为单位的压缩机。
  3. 解压缩程序的长度(如果与压缩程序不同),以字符为单位。

您应该在回答中记下所有三个计数及其总和。由于这是标准高尔夫,得分越低越好。

规则和限制:

  • 可能不会使用任何预先存在的压缩或解压缩工具或库,即使它们与您选择的语言捆绑在一起也是如此。如果不确定是否允许使用给定的工具或功能,请询问。

  • 您的压缩程序必须能够处理由任何可打印ASCII文本组成的输入,包括制表符(ASCII 9)和换行符(ASCII 10)。您可能但不是必须处理任意Unicode和/或二进制输入。

  • 您的解压缩程序必须产生作为输入的压缩器完全相同的输出。特别是,如果输入中没有尾随换行符,请注意不要输出。(下面的测试输入确实包含尾随换行符,因此您需要对此进行单独测试。GolfScript提示:'':n。)

  • 您的压缩器和解压缩器可能是同一程序(选择了适当的模式,例如使用命令行开关)。在这种情况下,其长度仅计算一次

  • 程序不应过慢或占用大量内存。如果压缩或解压缩测试输入在不是很新的台式机(2.2GHz AMD Athlon64 X2)上花费了超过一分钟,或者消耗了超过1 GB的RAM,那么我将裁定该解决方案无效。这些限制是故意放宽的-请尽量不要推动它们。(请参阅下面的修订:在这些限制之内,您必须至少能够处理100 kB的输入。)

  • 即使仅测试输入对评分很重要,您也至少努力压缩任意输入文本。针对测试输入即可达到不错的压缩比的解决方案在技​​术上是有效的,但不会对我不利。

  • 您的压缩程序和解压缩程序应该是独立的。特别是,如果它们依赖于能够读取不属于所选语言的标准运行时环境的一部分的文件或网络资源,则该文件或资源的长度应计为程序长度的一部分。(这是不允许“压缩器”将输入与网络上的文件进行比较,如果匹配则输出零字节。对不起,但这不再是新技巧了。)

修订和澄清:

  • 您的压缩器必须能够在合理的时间和内存使用量(最多一分钟,一GB内存)内处理至少100 kB的典型英文文本。您的解压缩器必须能够在相同限制内解压缩结果输出。当然,能够处理比该时间更长的文件是完全可以的,值得称赞。可以将长输入文件分成多个块并分别进行压缩,也可以使用其他方法来权衡压缩效率,以提高长输入的速度。

  • 您的压缩器可能需要使用首选平台的本机换行符表示形式(LF,CR + LF,CR等)来提供其输入,只要您的解压缩器在其输出中使用相同的换行符表示形式即可。当然,只要您的解压缩器输出与原始输入中相同类型的换行符,压缩器也可以接受任何类型的换行符(甚至不考虑平台,甚至仅接受Unix换行符)也是可以的。

测试输入:

为了判断答案的压缩效率,将使用以下测试输入(Edgar Allan Poe撰写的The Raven由Gutenberg项目提供):

Once upon a midnight dreary, while I pondered, weak and weary,
Over many a quaint and curious volume of forgotten lore,
While I nodded, nearly napping, suddenly there came a tapping,
As of some one gently rapping, rapping at my chamber door.
"'T is some visiter," I muttered, "tapping at my chamber door--
                                          Only this, and nothing more."

Ah, distinctly I remember it was in the bleak December,
And each separate dying ember wrought its ghost upon the floor.
Eagerly I wished the morrow:--vainly I had sought to borrow
From my books surcease of sorrow--sorrow for the lost Lenore--
For the rare and radiant maiden whom the angels name Lenore--
                                          Nameless here for evermore.

And the silken sad uncertain rustling of each purple curtain
Thrilled me--filled me with fantastic terrors never felt before;
So that now, to still the beating of my heart, I stood repeating
"'T is some visiter entreating entrance at my chamber door
Some late visiter entreating entrance at my chamber door;--
                                          This it is, and nothing more."

Presently my soul grew stronger; hesitating then no longer,
"Sir," said I, "or Madam, truly your forgiveness I implore;
But the fact is I was napping, and so gently you came rapping,
And so faintly you came tapping, tapping at my chamber door,
That I scarce was sure I heard you"--here I opened wide the door;--
                                          Darkness there, and nothing more.

Deep into that darkness peering, long I stood there wondering, fearing,
Doubting, dreaming dreams no mortal ever dared to dream before;
But the silence was unbroken, and the darkness gave no token,
And the only word there spoken was the whispered word, "Lenore!"
This I whispered, and an echo murmured back the word, "Lenore!"
                                          Merely this and nothing more.

Back into the chamber turning, all my soul within me burning,
Soon again I heard a tapping, somewhat louder than before.
"Surely," said I, "surely that is something at my window lattice;
Let me see, then, what thereat is, and this mystery explore--
Let my heart be still a moment and this mystery explore;--
                                          'T is the wind and nothing more!"

Open here I flung the shutter, when, with many a flirt and flutter,
In there stepped a stately Raven of the saintly days of yore.
Not the least obeisance made he; not a minute stopped or stayed he;
But, with mien of lord or lady, perched above my chamber door--
Perched upon a bust of Pallas just above my chamber door--
                                          Perched, and sat, and nothing more.

Then this ebony bird beguiling my sad fancy into smiling,
By the grave and stern decorum of the countenance it wore,
"Though thy crest be shorn and shaven, thou," I said, "art sure no craven,
Ghastly grim and ancient Raven wandering from the Nightly shore,--
Tell me what thy lordly name is on the Night's Plutonian shore!"
                                          Quoth the Raven, "Nevermore."

Much I marvelled this ungainly fowl to hear discourse so plainly,
Though its answer little meaning--little relevancy bore;
For we cannot help agreeing that no living human being
Ever yet was blessed with seeing bird above his chamber door--
Bird or beast upon the sculptured bust above his chamber door,
                                          With such name as "Nevermore."

But the Raven, sitting lonely on the placid bust, spoke only
That one word, as if his soul in that one word he did outpour.
Nothing further then he uttered--not a feather then he fluttered--
Till I scarcely more than muttered, "Other friends have flown before--
On the morrow _he_ will leave me, as my hopes have flown before."
                                          Then the bird said, "Nevermore."

Startled at the stillness broken by reply so aptly spoken,
"Doubtless," said I, "what it utters is its only stock and store,
Caught from some unhappy master whom unmerciful Disaster
Followed fast and followed faster till his songs one burden bore--
Till the dirges of his Hope that melancholy burden bore
                                          Of 'Never--nevermore.'"

But the Raven still beguiling all my sad soul into smiling,
Straight I wheeled a cushioned seat in front of bird and bust and door;
Then, upon the velvet sinking, I betook myself to linking
Fancy unto fancy, thinking what this ominous bird of yore--
What this grim, ungainly, ghastly, gaunt and ominous bird of yore
                                          Meant in croaking "Nevermore."

This I sat engaged in guessing, but no syllable expressing
To the fowl whose fiery eyes now burned into my bosom's core;
This and more I sat divining, with my head at ease reclining
On the cushion's velvet lining that the lamplight gloated o'er,
But whose velvet violet lining with the lamplight gloating o'er
                                          _She_ shall press, ah, nevermore!

Then, methought, the air grew denser, perfumed from an unseen censer
Swung by seraphim whose foot-falls tinkled on the tufted floor.
"Wretch," I cried, "thy God hath lent thee--by these angels he hath sent thee
Respite--respite and nepenthe from thy memories of Lenore!
Quaff, oh quaff this kind nepenthe, and forget this lost Lenore!"
                                          Quoth the Raven, "Nevermore."

"Prophet!" said I, "thing of evil!--prophet still, if bird or devil!--
Whether Tempter sent, or whether tempest tossed thee here ashore,
Desolate yet all undaunted, on this desert land enchanted--
On this home by Horror haunted--tell me truly, I implore--
Is there--_is_ there balm in Gilead?--tell me--tell me, I implore!"
                                          Quoth the Raven, "Nevermore."

"Prophet!" said I, "thing of evil--prophet still, if bird or devil!
By that Heaven that bends above, us--by that God we both adore--
Tell this soul with sorrow laden if, within the distant Aidenn,
It shall clasp a sainted maiden whom the angels name Lenore--
Clasp a rare and radiant maiden whom the angels name Lenore."
                                          Quoth the Raven, "Nevermore."

"Be that word our sign of parting, bird or fiend!" I shrieked, upstarting--
"Get thee back into the tempest and the Night's Plutonian shore!
Leave no black plume as a token of that lie thy soul hath spoken!
Leave my loneliness unbroken!--quit the bust above my door!
Take thy beak from out my heart, and take thy form from off my door!"
                                          Quoth the Raven, "Nevermore."

And the Raven, never flitting, still is sitting, still is sitting
On the pallid bust of Pallas just above my chamber door;
And his eyes have all the seeming of a demon's that is dreaming,
And the lamplight o'er him streaming throws his shadow on the floor;
And my soul from out that shadow that lies floating on the floor
                                          Shall be lifted--nevermore!

正确的测试输入(使用Unix风格的LF换行符编码)应为7043个字节长,并具有十六进制的MD5哈希值286206abbb7eca7b1ab69ea4b81da227。(md5sum -t即使在DOS / Windows上使用CR + LF换行符,也应产生相同的哈希值。)解压缩器的输出应具有相同的长度和哈希值。

附言 请记住,这一挑战只是您所面对的。实际上,任何低于7043的东西都算是一个好成绩。(在天平的另一端,我会非常若有人2500下实现了深刻的印象分)


所以我认为您不想看到任何有损压缩?
骆马先生2012年

2
对于无法获得MD5哈希值匹配的人的优先注意事项:文本文件具有Unix换行符以用作行尾。另外,请确保文件中具有最后的换行符,以获取7043字节的完整长度。
骆马先生2012年

@GigaWatt:是的,关于换行符,我应该更明确了。由于我仅将输入限制为ASCII文本,因此我猜想我可以允许人们使用对他们而言最自然的换行符约定,只要他们始终使用它即可。我将尝试思考一种在挑战中表达这一点的好方法。不,压缩机不应该有损。
Ilmari Karonen 2012年

文件长度如何?是否仅在示例大小顺序的文件中运行(在可接受的时间内),还是在更大的文件(> MB以上)中运行?
停止转动逆时针

1
如果以与压缩程序相同的语言以程序形式给出输出,那么我们可以算出解压缩程序的长度为零吗?
彼得·泰勒

Answers:


19

Perl,3502 = 133 + 3269 + 100

编码器:

#!/usr/bin/perl -0
$_=<>;for$e(map{~chr}0..255){++$p{$_}for/..|.\G./gs;
%p=$s=(sort{$p{$a}<=>$p{$b}}keys%p)[-1];$d.=/\Q$e/?$/:s/\Q$s/$e/g&&$s}print$_,$d

和解码器:

#!/usr/bin/perl -0777
sub d{($p=$d{$_})?d(@$p):print for@_}
sub r{%d=map{chr,ord($c=pop)&&[pop,$c]}0..255;&d}r<>=~/./gs

对于希望避免使用命令行开关的纯粹主义者:您可以删除shebang行,然后将其添加$/=chr;到编码器和$/=$,;解码器中,以达到相同的效果。(这会将分数提高到3510。)

这段代码使用了非常原始的压缩方案:

  • 查找在源文本中最频繁出现的两个字符的二元组。
  • 用当前未使用的字节值替换bigram。
  • 重复直到不再有重复的二元组(或不再有未使用的字节值)为止。

外面的人可能会认为这是“修复”压缩的简化版本(递归对的缩写)。

这不是一个很好的通用压缩方案。它仅适用于ASCII文本之类的东西,因为那里有许多未使用的字节值,即使这样,它通常也不会超过45-50%的比率。但是,它的优点是可以用最少的代码实现。特别地,解压缩器可以非常紧凑。(我的解码器脚本中的大多数字符都用于检索bigram字典。)

这是该代码的原始版本:

#!/usr/bin/perl
use strict;
use warnings;
# Run with -d to decode.
if ($ARGV[0] eq "-d") {
    shift;
    $_ = join "", <>;
    my @in = split //;
    my %dict;
    foreach my $n (0 .. 255) {
        my $c = shift @in;
        $dict{chr $n} = [ $c, shift @in ] if ord $c;
    }
    sub decode {
        foreach (@_) {
            if ($dict{$_}) {
                decode(@{$dict{$_}});
            } else {
                print $_;
            }
        }
    }
    decode @in;
} else {
    $_ = join "", <>;
    my @dict;
    for (my $n = 255 ; $n >= 0 ; --$n) {
        my $symbol = chr $n;
        if (!/\Q$symbol/) {
            my %pop;
            ++$pop{$_} for /../gs, /(?!^)../gs;
            my $str = (sort { $pop{$b} <=> $pop{$a} } keys %pop)[0];
            s/\Q$str/$symbol/g;
            $dict[$n] = $str;
        }
    }
    for (0..255) { $dict[$_] ||= "\0" }
    print @dict, $_;
}

我认为,高尔夫球编码器中的一种表达方式需要解释,也就是说(sort{$p{$a}<=>$p{$b}}keys%p)[-1],要获得具有最高价值的钥匙。看起来应该写成(sort{$p{$b}<=>$p{$a}}keys%p)[0],它做同样的事情,但短了一个字符。我之所以没有这样写,是因为当有多个具有最高值的键时,它会更改所选键。碰巧的是,这导致测试输入的结果输出长了10个字节。我讨厌承担无用的额外角色,但不足以牺牲我的得分9分。

面对您,Golfscript!(哈哈,如果能听到我的声音,Golfscript会完全过来打我的屁股。)


3
哇,真是令人印象深刻!附言 似乎是关于命令行开关计数的普遍接受的答案。
Ilmari Karonen

ang,我较早读过,但我没注意到中间的那一点。听起来像是这样:您不计算初始连字符(因为您可以将其添加到-e选项包中),除非您的代码包含单引号字符,在这种情况下您要计算连字符(因为现在您必须从带有shebang行的文件中运行它,以避免为在命令行上转义单引号而付费)。
面包箱

1
该技术也称为字节对编码。不错的实现方式
roblogic

@roblogic感谢您的参考;很高兴知道。
面包箱

20

Python,3514 = 294 + 2894 + 326

基本上是bzip2实现。它进行了Burrows-Wheeler变换前移变换,简单的霍夫曼编码为位流,然后将该位流转换为整数并写出字节。

编码器:

import sys
S=range(128)
H={0:'0'}
for b in range(7):
 for i in range(1<<b,2<<b):H[i]='1'*b+'10'+bin(i)[3:]
I=sys.stdin.read()+'\0'
N='1'
for x in sorted(I[i:]+I[:i]for i in range(len(I))):i=S.index(ord(x[-1]));N+=H[i];S=[S[i]]+S[:i]+S[i+1:]
N=int(N,2)
while N:sys.stdout.write(chr(N%256));N>>=8

S是向前移动队列,H是霍夫曼编码器,N是比特流。

编码将测试输入减小到其原始大小的约41%。

解码器:

import sys
N=0
b=1
for c in sys.stdin.read():N+=ord(c)*b;b<<=8
N=bin(N)[3:]
S=range(128)
L=''
while N:
 n=N.find('0')
 if n:i=2**n/2+int('0'+N[n+1:2*n],2);N=N[2*n:]
 else:i=0;N=N[1:]
 L+=chr(S[i]);S=[S[i]]+S[:i]+S[i+1:]
S=''
i=L.find('\0')
for j in L:S=L[i]+S;i=L[:i].count(L[i])+sum(c<L[i]for c in L)
sys.stdout.write(S[:-1])

1
我很想实现BWT并做真正的压缩形式,但是太懒了。:P
Llama先生2012年

8

8086汇编程序/ MS_DOS

压缩机155

jNiAxBCO2I7AM/+9/QW5AAGK2TPAq4rDqv7D4va6AQkz9lK0BrL/zSFadDK7
/f+DwwM733QNOTd19ThHAnXwid7r34k1iEUC6BMAtACKRQJr8AODxwPryrQC
zSHrxFIz0ovGuwMA9/Nai9iKztPL0ePQ0nMWgPr+cgtSsv60Bs0hWoDq/rQG
zSGyAf7JdeA5/XUHA+2DxQP+xsM=

数据:3506

解压缩器:203

ieWD7CCM2IDEEI7YjsAz/7kAAYrZM8CrisOq/sPi9rYJxkb0Abn9BehtAIl2
/uhTAOhkAIl28Dv3cy3oRgCLRv6JBYt28Il2/oM8AHQEizTr94pEAohFAoPH
AznPddL+xgPJg8ED68mLdv6JNYM8AHQEizTr94pEAohFAol+/on+aFgBgzwA
dAdWizTo9f9etAaKVALNIcMz9ojz/k70dRu0BrL/zSF0IDz+cgi0BrL/zSEE
/sZG9AiIRvLQZvLR1v7Ldddr9gPDzSA=

合计:3864

使用此Base64解码器并将二进制文件另存为'compress.com'和'decompress.com',然后执行以下操作:

compress < source > compressed_file
decompress < compressed_file > copy_of_source

在DOS外壳中(经过WinXP测试)。没有错误检查,因此压缩大文件将产生错误的结果。一些小的补充,它可以应付任何大小的文件。同样,它不能解压缩为二进制文件,因为它无法输出0xff值(压缩数据将0xff值转义为0xfe 0xff,将0xfe转义为0xfe 0xfe)。使用命令行文件名将克服二进制输出问题,但将是更大的可执行文件。


程序使用哪种压缩算法?
Sir_Lagsalot

@Sir_Lagsalot:它使用可变的位宽LZW(在GIF文件中使用的宽度)。
Skizz'2

6

重击诗(566 + 117)+ 4687 = 5370

为了好玩,我把一台压缩机装扮成一首诗:

for I in my chamber nodded, nearly napping, suddenly heard rapping, tapping upon my door    \
"'T is some visiter" \ I\  muttered, o\'er lamplight "nothing more" \
just this sainted maiden whom the angels name Lenore    \
And "Prophet!" said me "thing of evil" -- "prophet still, if bird or devil!"    \
Leave no token of that lie thy soul hath spoken and sitting take thy ore from This floor    \
But you velvet bird from some shore above   \
here this with sad raven before his word still spoke nothing    \
"                                          " Quoth the Raven Never more;                    do C=$[C+1];E=`perl -e "print chr($C+128)"`;echo "s/$I/$E/g">>c;echo "s/$E/$I/g">>d;done;LANG=C sed -f $1;rm c d

这是一个统一的压缩器:使用选项“ c”运行将进行压缩,而使用“ d”运行将进行解压缩。它分为两部分:一首566字节的“读者摘要”版本的诗歌,以及(2)117字节的后缀,其中完成了所有“真实”的重击。

小心谨慎(例如,以“ for for in”开头的诗),bash会将诗的“有损”版本解释为数组。它将数组的每个元素替换为非ASCII字符(我们假设输入为ASCII,因此不存在冲突)。该解决方案的一个次要优势是:由于我们利用了可以假定输入为ASCII的事实,因此无论输入和/或有损部分是什么,此压缩的输出都不会比其输入长。

最接近违反的规则是在其他文本上提供适当的压缩率的规则。但是,它比GPL V2文本少了1386个字节,超过了它自己的大小,这似乎与的OP定义相匹配decent。因此,它似乎为decent一般文本提供了所谓的压缩。这是因为几乎所有英文文本都带有“ the”,“ that”等。显然,如果将“有损”部分替换为类似于您要无损压缩的原始文本,则效果会更好。

将图片和音频分为有损和无损部分是一种已知技术。这对于文本而言效果不佳:即使从有损版本中排除了566个字节,4687字节也不是那么好,并且我们无法像音频一样真正自动生成有损版本的文本。从好的方面来说,这意味着每次您使用此压缩器压缩内容时,都可以手动创建有损版本。因此,这似乎是一个合理的“有趣”解决方案。


5

C ++,4134字节(代码= 1357,压缩= 2777)

这样做会像Keith Randall一样进行Burrows-Wheeler变换+向前移动,但是随后使用自适应Range Coder压缩结果字节序列。不幸的是,范围编码器改善的压缩效果不足以抵消C ++的冗长性。我可以进一步研究此代码,即使用不同的输入/输出方法,但这不足以用当前算法击败其他提交。该代码特定于Windows,仅支持ascii文本。
要压缩:“ C text_filecompressed_file”
要解压缩:“ Dcompressed_file uncompressed_file”
几乎任何命令行错误或文件错误都会使程序崩溃,并且花费一分钟的大部分时间来编码或解码这首诗。

#include <windows.h>
#include <algorithm>
typedef DWORD I;typedef BYTE u;
#define W while
#define A(x)for(a=0;a<x;a++)
#define P(x)*o++=x;
I q,T=1<<31,B=T>>8,a,l,f[257],b,G=127,p=G,N=255;I Y(u*i,u*j){return
memcmp(i,j,l)<0;}I E(u*i,u*o){b=0;I L=0,h=0,R=T;u*c=o,*e=i+l;W(i<e){I
r=R/p,s=0;A(*i)s+=f[a];s*=r;L+=s;R=*i<N?r*f[*i++]++:R-s;p++;W(R<=B){if((L>>23)<N){for(;h;h--)P(N)P(L>>23)}else{if(L&T){o[-1]++;for(;h;h--)P(0)P(L>>23)}else
h++;}R<<=8;L<<=8;L&=T-1;}}P(L>>23)P(L>>15)P(L>>7)return
o-c;}void D(u*i,u*o){I R=128,L=*i>>1;u*e=o+l;W(o<e){W(R<=B){L<<=8;L|=((*i<<7)|(i++[1]>>1))&N;R<<=8;}I
h=R/p,m=L/h,x=0,v=0;W(v<=m)v+=f[x++];P(--x);L-=h*(v-f[x]);R=h*f[x]++;p++;}}void
main(I Z,char**v){u d[1<<16];I c=*v[1]<68,s;HANDLE F=CreateFileA(v[2],T,0,0,3,0,0),o=CreateFileA(v[3],T/2,0,0,2,0,0);ReadFile(F,d,GetFileSize(F,0),&l,0);l=c?l:*(I*)d;A(G)f[a]=1;u M[256];A(G)M[a]=a+1;u*g=new u[l*3],*h=g+l;if(c){memcpy(d+l,d,l);u**R=new
u*[l];A(l)R[a]=d+a;std::sort(R,R+l,Y);A(l){b=R[a][l-1];I
i=strchr((char*)M,b)-(char*)M;memmove(M+1,M,i);*M=g[a]=b;h[a]=i;}s=E(h,d+l+8);}else{D(d+8,g);A(l){I
k=g[a];g[a]=M[k];memmove(M+1,M,k);*M=g[a];}}u**j=new u*[l];A(l)j[a]=new
u[l*2],memset(j[a],0,l*2),j[a]+=l;A(l){for(b=0;b<l;)*--j[b]=g[b++];std::sort(j,j+l,Y);}if(c){A(l){if(!memcmp(j[a],d,l)){I*t=(I*)(d+l);*t=l;t[1]=a;g=d+l,l=s+8;}}}else
g=j[*(I*)(d+4)];WriteFile(o,g,l,&q,0);}

5

JavaScript 393(代码)+ 3521(测试)= 3914(总计)

该程序迭代地将未使用的字节值替换为输入的2至4个字符的块。根据原始块的频率和长度对每个替换进行评分,并且每次都选择最佳替换。如果我能找出相对较少的字符数,那么我将添加一个最后的霍夫曼编码阶段。减压本质上是一系列查找和替换操作。

用法

C()提供压缩;U()提供解压缩。由于JavaScript的字符串基于16位Unicode代码单元,因此压缩数据格式仅使用每个代码单元的最低有效8位。这与Firefox的btoa()和atob()函数兼容,用于Base64编码。(用法示例

由于.replace()的非标准“ g”选项,因此该程序只能在Firefox中运行。

高尔夫代码:

S=String.fromCharCode;function C(c){h=[];for(f=0;129>f;++f){g='';i=0;for(e=2;5>e;++e){d={};for(a=0;a<=c.length-e;a+=e)b="K"+c.substr(a,e),d[b]=d[b]?d[b]+1:1;for(b in d)a=d[b],a=a*e-(1+e+a),a>i&&(g=b.slice(1),i=a)}if(!g)break;h[f]=g;c=c.replace(g,S(127+f),"g")}return h.join("\1")+"\1"+c}function U(a){c=a.split("\1");a=c.pop();for(b=c.length,d=127+b;b--;)a=a.replace(S(--d),c[b],"g");return a}

打高尔夫球之前:

function compress(str) {

    var hash, offset, match, iteration, expansions, bestMatch, bestScore, times, length, score;

    expansions = [];

    for (iteration = 0; iteration < 129; ++iteration) {

        bestMatch = null;
        bestScore = 0;

        for (length = 2; length < 5; ++length) {

            hash = {};

            for (offset = 0; offset <= str.length - length; offset += length) {
                match = 'K' + str.substr(offset, length);
                hash[match] = hash[match] ? hash[match] + 1 : 1;
            }

            for (match in hash) {
                times = hash[match];
                score = times * length - (1 + length + times);
                if (score > bestScore) {
                    bestMatch = match.slice(1);
                    bestScore = score;
                }
            }

        }

        if (!bestMatch) {
            break;
        }

        expansions[iteration] = bestMatch;
        str = str.replace(bestMatch, String.fromCharCode(127 + iteration), 'g');

    }

    return expansions.join('\u0001') + '\u0001' + str;
}

function uncompress(str) {
    var i, j, expansions;

    expansions = str.split('\u0001');
    str = expansions.pop();

    for (j = expansions.length, i = 127 + j; j--;) {
        str = str.replace(String.fromCharCode(--i), expansions[j], 'g');
    }

    return str;
}

为什么会得到C(text).length=7301?(FF 60.0.2)
平方米,

3

PHP(347 + 6166 + 176)= 6689

因此,我采用了一种简单的字典+替换方法。

如果一个单词出现多次,并且(将单词编码+保存替换条目)时间较短,那么它将进行替换。如果“单词”恰好是数字,则无论如何都要这样做以防止在减压期间意外替换。替换的“字典”由空字节,两个空字节,然后是替换所依据的主体组成。

可能的改进:

  • Windows不喜欢传送超过4kb的数据,因此找到一种比使用文件更好的方法。
  • 匹配长字符串的空白并将其视为“单词”的能力,而无需添加太多代码。
  • 提出更好的替代方法,而不是使用数字。

用法:压缩程序查找名为“ i”的文件,并将压缩后的数据写入“ o”。解压缩器查找“ o”,并将未压缩的数据写入“ d”。这是我对Windows不满意的替代方法,它不喜欢发送大量数据。


compress.php(347)

<?$d=file_get_contents('i');$z=chr(0);preg_match_all('|\b(\w+)\b|',$d,$m);$n=0;foreach($m[0]as$w){$l=strlen($w);$q[$w]=isset($q[$w])?$q[$w]+$l:$l;}arsort($q);foreach($q as$w=>$s){$l=strlen($w);$c=$s/$l;if($c*strlen($n)+$l<$s||is_int($w)){$d=preg_replace('|\b'.preg_quote($w).'\b|',$n++,$d);$f[]=$w;}}file_put_contents('o',implode($z,$f).$z.$z.$d);

带有注释和解释的扩展版本


输出没有字典的样本。有点有趣的看。
正常大小:6166

Ah, distinctly I remember it 45 in 0 bleak December,
25 each separate dying ember wrought its ghost 39 0 37.
Eagerly I wished 0 88:--vainly I had sought to borrow
From 9 books surcease of 43--43 for 0 lost 8--
For 0 rare 1 67 40 54 0 26 38 8--
                                          Nameless 63 for evermore.

25 0 silken sad uncertain rustling of each purple curtain
Thrilled me--filled me 19 fantastic terrors never felt 17;
So 4 now, to 13 0 beating of 9 64, I stood repeating
"'T is 57 31 36 49 at 9 2 5
Some late 31 36 49 at 9 2 5;--
                                          58 it is, 1 10 16."

decompress.php(176)

<?$z=chr(0);$d=file_get_contents('o');list($w,$d)=explode($z.$z,$d);$w=explode($z,$w);$n=0;foreach($w as$r){$d=preg_replace('|\b'.$n++.'\b|',$r,$d);};file_put_contents('d',$d);

扩展版并附有说明。


欢迎提出任何改进建议。

编辑:添加了代码的“展开”版本,并添加了大量注释。应该很容易理解。


加!与我使用的语言和方法相同!该死 尽管我还没有跳过单个单词。
Gareth 2012年

当文本中有数字时会发生什么?最终将用不适当的单词替换原始数字。尽管我采用了类似的方法(正则表达式拆分,查找要替换的常用词并生成替换字典并使用null粘贴),但我使用了unicode字符而不是数字(从chr(128)开始,因为此后的任何内容在标准ascii)
开拓者

@Blazer:实际上,已经有适当的代码(即||is_int($w))通过始终将数字添加到字典中来处理数字,但是这似乎是有问题的:在压缩和解压缩整个 Gutenberg电子文本之后,输出以开头The 4 3 EBook 2 The Raven, by Edgar Allan Poe。:-(我怀疑的问题是,什么是越来越换过两次,你可能要考虑使用strtr()来避免这个问题。
ILMARI Karonen

@Ilmari(如果您有大量数字的文档),将这些数字添加到字典中可能导致压缩比原始压缩大。存储几个1-2个字符长的项目无效。就像您要替换文档中的“ a”一词一样
Blazer 2012年

@Blazer-对于所有压缩算法,都有某些输入将导致更大的输出。它是无损压缩所固有的,就像无法可靠地压缩熵数据一样。
骆马先生2012年

3

GolfScript 3647(压缩大小3408 +代码大小239)

128,{[.;]''+}%:d;8:k;{2k?={1k+:k;}*}:|;{2base}:b;{.[0]*@b+0@->}:$;.0=
{'':&,:i;1/{.d&@+?.0<{;d,i@d&@:&.0=:i;[+]+:d;k$\|}{:i;&\+:&;}if}%[0]k*+[]*8/{b}%"\0"\+}
{1>{8$}/][]*:^;{^k<b^k>:^;}:r~{.}{d,|d=:&r..d,<{d=}{;&}if[1<&\+]d\+:d;}while;}if

使用的算法是带有可变宽度代码的LZW压缩。第一行是共享代码,第二行是压缩代码,第三行是解压缩代码。

它处理ASCII字符在1-127范围内的文件,并且自动识别压缩文件(它们以0字节开头),因此不需要解压缩参数。

示例运行:

$ md5sum raven.txt
286206abbb7eca7b1ab69ea4b81da227  raven.txt
$ ruby golfscript.rb compress.gs < raven.txt > raven.lzw
$ ls -l raven.lzw
-rw-r--r-- 1 ahammar ahammar 3408 2012-01-27 22:27 raven.lzw
$ ruby golfscript.rb compress.gs < raven.lzw | md5sum
286206abbb7eca7b1ab69ea4b81da227  -

注意:在添加处理100kb的要求之前,我已经很早就开始了,所以我还没有在该大小的输入上对其进行测试。但是,压缩测试输入大约需要30秒,而解压缩则需要5秒,峰值时大约需要20MB的内存。


压缩76 KB的文件似乎需要约19分钟,而解压缩需要10.这一种缓慢的,但话又说回来,它通过原来的规则,所以......我不知道。在这种情况下不允许这样做似乎有点不公平。我想我可以为您或其他人调用一个隐式的“祖父条款”。
Ilmari Karonen 2012年

3

哈斯克尔3973

晚会晚了,不会赢,但是我写它很有趣,所以我不妨发表。

这是LZW的简单可变宽度实现,其字典明确限制为可打印的ASCII,制表符和换行符。不带参数运行,它将标准输入压缩为file C。使用任何参数运行(但是“ --decompress”是一个合理的选择),它将文件解压缩C为标准输出。

import List
import System
import Data.Binary
q=head
main=getArgs>>=m
m[]=getContents>>=encodeFile"C".s 97 128 1 0.e 97h
m _=decodeFile"C">>=putStr.d tail""96h.u 97 128
h=zip[0..].map(:[])$"\t\n"++[' '..'~']
e _ _[]=[]
e n s y=c:e(n+1)((n,take(1+l)y):s)(drop(l)y)where{Just(c,p)=find((`isPrefixOf`y).snd)s;l=length p}
d _ _ _ _[]=""
d f p n s(x:y)=t++d id t(n+1)(f$(n,p++[q t]):s)y where t=maybe(p++[q p])id$lookup x s
s _ _ _ a[]=a::Integer
s n w o a y|n>w=s n(2*w)o a y|0<1=s(n+1)w(o*w)(a+o*q y)(tail y)
u _ _ 0=[]
u n w x|n>w=u n(2*w)x|0<1=(x`mod`w::Integer):u(n+1)w(x`div`w)
  • 码大小:578
  • 压缩样本大小:3395
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.