テキストの圧縮と解凍—「Nevermore」。


38

コードゴルフでの圧縮ツールの使用に関する最近の議論で、独自のテキストコンプレッサーとデコンプレッサーを作成することは素晴らしい挑戦になると思いました。

チャレンジ:

2つのプログラムを作成します。1つはASCIIテキストをバイトシーケンスに圧縮し、もう1つは圧縮解除します。プログラムは同じ言語である必要はありません。

最初のプログラムは、ASCIIファイルを(ファイルから、または標準入力から、または言語にとって最も自然なメカニズムを使用して)読み取り、その圧縮バージョンを出力する必要があります。(圧縮された出力は、任意のバイトで構成されている場合があります。読み取り可能である必要はありません。)2番目のプログラムは、最初の出力を読み取り、元の入力テキストを再作成します。

得点:

ソリューションのスコアは、次の3つのカウントの合計になります。

  1. 圧縮プログラムの長さ(文字数)。
  2. 以下のテスト入力が与えられた場合の、コンプレッサーの出力長さ(バイト単位)。
  3. 圧縮解除プログラム(圧縮器と異なる場合)の長さ(文字数)。

3つのカウントすべてとその合計を回答に書き留めてください。これはコードゴルフであるため、スコアが低いほど優れています。

ルールと制限:

  • 選択した言語にバンドルされている場合でも、既存の圧縮または解凍ツールまたはライブラリを使用することはできません。特定のツールまたは機能が許可されているかどうか疑問がある場合は、お問い合わせください。

  • 圧縮プログラムは、タブ(ASCII 9)および改行(ASCII 10)を含む印刷可能なASCIIテキストで構成される入力を処理できる必要があります。任意のUnicodeおよび/またはバイナリ入力を処理できますが、必須ではありません。

  • 圧縮解除プログラムは、入力として圧縮器に与えられたものとまったく同じ出力を生成する必要があります。特に、入力に末尾の改行が含まれていない場合は、末尾の改行を出力しないように注意してください。(以下のテスト入力には末尾の改行があるため、これを個別にテストする必要があります。GolfScriptのヒント:'':n。)

  • 圧縮プログラムと圧縮解除プログラムは同じプログラムである場合があります(コマンドラインスイッチなどで適切なモードが選択されている場合)。その場合、その長さは一度だけカウントされます。

  • プログラムの速度極端に遅くなったり、メモリを大量に消費することはありません。あまり新しくないデスクトップ(2.2GHz AMD Athlon64 X2)でテスト入力を圧縮または解凍するのに1分以上かかる場合、または1ギガバイトを超えるRAMを消費する場合、ソリューションを無効と判断します。これらの制限は意図的に緩いものです。プッシュしないようにしてください。(以下の修正を参照してください。これらの制限内で少なくとも100 kBの入力を処理できる必要があります。)

  • スコアリングにはテスト入力のみが重要ですが、少なくとも任意の入力テキストを圧縮するよう努力する必要があります。テスト入力に対してのみ適切な圧縮率を達成するソリューションは、技術的には有効ですが、私からは賛成を得られません。

  • 圧縮プログラムと圧縮解除プログラムは自己完結型である必要があります。特に、選択した言語の標準ランタイム環境の一部ではないファイルまたはネットワークリソースを読み取ることができることに依存している場合、そのファイルまたはリソースの長さは、プログラムの長さの一部としてカウントする必要があります。(これは、入力をWeb上のファイルと比較し、一致する場合は0バイトを出力する「コンプレッサー」を許可しないためです。申し訳ありませんが、これは新しいトリックではありません。)

修正と説明:

  • コンプレッサーは、妥当な時間とメモリ使用量(最大1分、1 GBのメモリ)内で、少なくとも100 kBの典型的な英語テキストで構成されるファイルを処理できる必要があります。圧縮解除プログラムは、結果の出力を同じ制限内で圧縮解除できる必要があります。もちろん、それよりも長いファイルを処理できるということは完全にすばらしいことであり、賞賛に値します。長い入力ファイルをチャンクに分割して個別に圧縮したり、他の手段を使用して圧縮効率と長い入力の速度を犠牲にしたりしてもかまいません。

  • 圧縮解除プログラムが出力で同じ改行表現を使用している限り、圧縮プログラムでは、優先プラットフォームのネイティブ改行表現(LF、CR + LF、CRなど)を使用して入力を指定する必要あります。もちろん、コンプレッサーが元の入力と同じ種類の改行を出力する限り、コンプレッサーがあらゆる種類の改行(またはプラットフォームに関係なくUnixの改行のみ)を受け入れることも問題ありません。

テスト入力:

回答の圧縮効率を判断するために、次のテスト入力(エドガーアランポーによるレイヴン、Project 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バイト長で、16進数のMD5ハッシュが必要286206abbb7eca7b1ab69ea4b81da227です。(md5sum -tDOS / WindowsでCR + LF改行を使用しても、同じハッシュ値を生成するはずです。)解凍プログラムの出力は、同じ長さとハッシュを持つ必要があります。

追伸 この挑戦はあなたがそれをするのと同じくらい難しいだけであることに留意してください。本当に、7043未満は良いスコアとしてカウントされます。(スケールの反対側で、誰かが2500未満のスコアを達成した場合、私は非常に感銘を受けます。)


だから私はあなたが損失のある圧縮を見たくないと思っていますか?
ラマ氏

2
一致するMD5ハッシュを取得できない人のための先制メモ:テキストファイルには、行末コード用のUnix改行があります。また、ファイルに完全な7043バイト長の最後の改行があることを確認してください。
氏ラマ

@GigaWatt:ええ、私は改行についてもっと明確にすべきでした。入力をASCIIテキストのみに制限しているので、一貫して使用している限り、人々にとって最も自然な改行規則を使用できるようにすることができると思います。これをチャレンジで表現するいい方法を考えてみます。いいえ、コンプレッサーは損失がないはずです。
イルマリカロネン

ファイルの長さについてはどうですか、例のサイズの順序のファイルに対してのみ(許容時間内に)実行する必要がありますか、それともはるかに大きいファイル(> MB)に対しても実行する必要がありますか
反時計回りに

1
出力が圧縮プログラムと同じ言語のプログラムとして提供されている場合、圧縮解除プログラムの長さをゼロとしてカウントできますか?
ピーターテイラー

回答:


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

コマンドラインスイッチの使用を避けることを好む純粋主義者の場合:シェバンラインを削除し$/=chr;て、エンコーダーと$/=$,;デコーダーに追加して同じ効果を得ることができます。(これにより、スコアは3510になります。)

このコードは、非常に原始的な圧縮スキームを使用しています。

  • ソーステキストで最も頻繁に表示される2文字のバイグラムを見つけます。
  • バイグラムを現在使用されていないバイト値に置き換えます。
  • バイグラムが繰り返されなくなるまで(または未使用のバイト値がなくなるまで)繰り返します。

誰かがこれを「リペア」圧縮の単純化されたバージョン(再帰ペアの略)として認識するかもしれません。

一般的な圧縮方式としてはあまり適していません。ASCIIテキストなど、未使用のバイト値が多数ある場合にのみうまく機能し、それでも通常は45〜50%の比率しか得られません。ただし、最小限のコードで実装できるという利点があります。特に、減圧装置は非常にコンパクトです。(私のデコーダスクリプトのほとんどの文字は、バイグラム辞書を取得するためのものです。)

コードの未使用バージョンは次のとおりです。

#!/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, $_;
}

ゴルフエンコーダーでの表現の1つに(sort{$p{$a}<=>$p{$b}}keys%p)[-1]、最高の値を持つキーを取得するための説明が必要だと思います。それはとして書かれるべきであるように見えます(sort{$p{$b}<=>$p{$a}}keys%p)[0]。これは同じことをし、1文字短くなります。そのように記述しなかった理由は、最高値のキーが複数ある場合に、選択されたキーを変更するためです。偶然、これによりテスト入力の結果の出力が10バイト長くなりました。私は役に立たない余分なキャラクターを引き受けることを嫌いましたが、私のスコアから9ポイントを犠牲にするのに十分ではありませんでした。

あなたの顔に、Golfscript!(ハハ、Golfscriptは完全にここに来て、私の声が聞こえたら尻を蹴ります。)


3
うわー、それはかなり印象的です!追伸 これは、コマンドラインスイッチのカウントに関して一般に受け入れられている答えのようです。
イルマリカロネン

ダン、私はそれを以前読んだが、私は真ん中にそのビットに気付かなかった。つまり-e、コードにシングルクォート文字が含まれていない限り、最初のハイフン文字はカウントしません(オプションバンドルに追加するだけなので)、その場合はハイフンをカウントしますコマンドラインで一重引用符をエスケープするための支払いを避けるために、今度はシェバン行のあるファイルから実行する必要があります)。
ブレッドボックス

1
テクニックは、バイトペアエンコーディングとも呼ばれます。素敵な実装
ロブログ

@roblogic参照いただきありがとうございます。知っておくといいです。
パンボックス

20

Python、3514 = 294 + 2894 + 326

基本的にbzip2の実装。これはないバローズ-ウィーラー変換移動から前方変換、単純なハフマン符号化を整数に、ビットストリームに変換するビットストリームとバイトを書き込みます。

エンコーダー:

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
ラマ氏

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値を出力できないため、バイナリに解凍できません(圧縮データは0xfe値を0xfe 0xffとしてエスケープし、0xfeは0xfe 0xfeとしてエスケープします)。コマンドラインファイル名を使用すると、バイナリ出力の問題は解決されますが、実行可能ファイルは大きくなります。


プログラムはどのような圧縮アルゴリズムを使用していますか?
Sir_Lagsalot

@Sir_Lagsalot:可変ビット幅LZW(GIFファイルで使用されるもの)を使用します。
スキズ

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」では圧縮解除されます。これには2つの部分があります。566バイトの「リーダーダイジェスト」バージョンの詩と(2)117バイトのサフィックスで、すべての「実際の」bashが行われます。

いくつかの注意を払って(たとえば、 "for I in"で詩を開始する)bashは、詩の "lossy"バージョンを配列として解釈します。配列の各要素を非ASCII文字に置き換えます(入力はASCIIであるため、衝突は発生しません)。このソリューションの1つの小さな利点:入力がASCIIであると想定できるという事実を利用するため、この圧縮の出力は、入力や損失のある部分が何であるかに関係なく、入力より長くなることはありません。

これが違反に最も近いという規則は、他のテキストに適切な圧縮率を提供することに関する規則です。ただし、GPL V2テキストから1386バイトを削り取り、独自のサイズを大幅に超えます。これは、のOP定義に一致するようですdecent。したがってdecent、一般的なテキストにいわゆる圧縮を提供するようです。これは、ほとんどすべての英語のテキストに「the」「that」などがあるためです。明らかに、「lossy」部分を可逆圧縮したいオリジナルに似たテキストに置き換えると、うまく機能します。

画像と音声を損失の多い部分損失のない部分に分割することは、既知の手法です。これはテキストに対しても機能しません:不可逆バージョンから566バイトを除外しても4687バイトはそれほど大きくなく、音声と同じように不可逆バージョンのテキストを実際に自動的に生成することはできません。プラス面では、このコンプレッサーで何かを圧縮するたびに、損失のあるバージョンを手作業で作成する楽しさを得ることができます。したがって、これは合理的な「楽しい」ソリューションのようです。


5

C ++、4134バイト(コード= 1357、圧縮= 2777)

これは、Burrows-Wheeler変換とKeith RandallのようなMove-To-Frontを行いますが、適応範囲コーダーを使用して結果のバイトシーケンスを圧縮します。残念ながら、レンジコーダーの改善された圧縮は、C ++の冗長性を相殺するのに十分ではありません。私はこのコードをもう少しゴルフすることができます。つまり、異なる入出力方法を使用しますが、現在のアルゴリズムで他の提出物を打ち負かすには十分ではありません。コードはWindows固有であり、ASCIIテキストのみがサポートされています。
圧縮するには: "C text_file compress_file"
解凍するには: "D compress_file uncompressed_file"
ほとんどすべてのコマンドラインエラーまたはファイルエラーによりプログラムがクラッシュし、詩のエンコードまたはデコードには1分以上かかります。

#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ビットのみが圧縮データ形式で使用されます。これは、Base64エンコーディング用のFirefoxのbtoa()およびatob()関数と互換性があります。(使用例

このプログラムは、.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)
l4m2

3

PHP、(347 + 6166 + 176)= 6689

それで、私は単純な辞書+置換アプローチを使いました。

単語が複数回出現し、単語のエンコード+置換エントリの保存が短い場合は、置換が行われます。「単語」が数字である場合、解凍中に誤って置換されるのを防ぐためにとにかくそれを行います。置換の「ディクショナリ」は、nullバイトで結合され、その後に2つのnullバイトが続き、その後に置換が機能する本文が続きます。

可能な改善:

  • Windowsは、4 kbを超えるデータをパイプ処理することを好まないため、ファイルを使用するよりも良い方法を見つけます。
  • 余白の長い文字列に一致し、コードを追加しすぎることなく「単語」としてカウントする機能。
  • 数字を使用する代わりに、より優れた代替を考え出す。

使用法:コンプレッサーは「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);

説明付きの拡張バージョン


改善のための提案を歓迎します。

編集:コードの「展開」バージョンを追加し、コメントを追加しました。簡単にフォローできるはずです。


ああ!私が使っていたのと同じ言語と方法!くそ 単語を1つスキップすることはできませんでしたが。
ガレス

テキスト内に数字があるとどうなりますか?その結果、元の数字が不適切な単語に置き換えられます。私は同様のアプローチを取りましたが(正規表現の分割、置換する一般的な単語の検索、置換辞書の作成、nullでの貼り付け)、数字の代わりにユニコード文字を使用しました(chr(128)から始まります。標準アスキー)
ブレイザー

@Blazer:実際には、||is_int($w)常に辞書に数字を追加することで数字を処理するコード(つまり)がありますが、バグがあるようです:Gutenberg E-text 全体を圧縮および解凍した後、出力はで始まりThe 4 3 EBook 2 The Raven, by Edgar Allan Poeます。:-(私は問題が何かが二度交換されていることだと思う; strtr()その問題を避けるために代わりに使用することを検討したいかもしれない。
Ilmari Karonen

@Ilmariには、数の多いドキュメントがある場合、それらの数字を辞書に追加すると、圧縮が元より大きくなる可能性があります。1〜2文字の長さのアイテムを複数保存することは効果的ではありません。あなたは言葉「」文書内を置換した場合のように
ブレイザー

@Blazer-すべての圧縮アルゴリズムには、出力が大きくなる特定の入力があります。エントロピーデータを確実に圧縮できないのと同様に、これはロスレス圧縮に固有のものです。
ラマ氏

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圧縮です。1行目は共有コード、2行目は圧縮コード、3行目は圧縮解除コードです。

1〜127の範囲のASCII文字を持つファイルを処理し、圧縮ファイルを自動的に認識します(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分かかります。これ少し遅いですが、それでも元のルールに合格するので...わからないです。状況下でそれを許可しないように不公平のようです。私はあなたまたは何かのために暗黙の「祖父句」を呼び出すことができると思います。
イルマリカロネン

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.