Compressione e decompressione del testo - "Mai più".


38

Con la recente discussione sull'uso degli strumenti di compressione nel code golf , ho pensato che sarebbe stata una bella sfida scrivere il proprio compressore di testo e decompressore.

Sfida:

Scrivi due programmi : uno per comprimere il testo ASCII in una sequenza di byte e un altro per decomprimerlo. I programmi non devono necessariamente essere nella stessa lingua.

Il primo programma dovrebbe leggere un pezzo di testo ASCII (da un file o da un input standard, o usando qualunque meccanismo sia più naturale per la lingua) e produrre una versione compressa di esso. (L'output compresso può essere composto o byte arbitrari; non è necessario che sia leggibile.) Il secondo programma dovrebbe leggere l'output del primo e ricreare il testo di input originale.

punteggio:

Il punteggio di una soluzione sarà la somma dei seguenti tre conteggi:

  1. La lunghezza del programma del compressore in caratteri.
  2. La lunghezza dell'output del compressore, dato l'input di test di seguito, in byte.
  3. La lunghezza del programma di decompressione (se diverso dal compressore) in caratteri.

Dovresti notare tutti e tre i conteggi e la loro somma nella tua risposta. Poiché si tratta di codice golf, più basso è il punteggio, migliore è.

Regole e restrizioni:

  • Non è possibile utilizzare strumenti o librerie di compressione o decompressione preesistenti, anche se forniti in bundle con la lingua scelta. In caso di dubbi sul permesso di un determinato strumento o funzione, si prega di chiedere.

  • Il programma del compressore deve essere in grado di gestire input costituito da qualsiasi testo ASCII stampabile , comprese le schede (ASCII 9) e i feed di riga (ASCII 10). È possibile, ma non è necessario, gestire Unicode arbitrario e / o input binario.

  • Il programma di decompressione deve produrre esattamente lo stesso output fornito al compressore come input. In particolare, fare attenzione a non generare un feed di riga finale se l'input non ne aveva uno. (L'input di prova di seguito ha un feed di riga finale, quindi dovrai provarlo separatamente. Suggerimento per GolfScript:. '':n)

  • Il compressore e il decompressore possono essere lo stesso programma (con la modalità appropriata selezionata, ad esempio con un interruttore della riga di comando). In tal caso, la sua lunghezza viene conteggiata una sola volta .

  • I programmi non dovrebbero essere eccessivamente lenti o affamati di memoria . Se comprimere o decomprimere l'input del test impiega più di un minuto sul mio desktop non così nuovo (2,2 GHz AMD Athlon64 X2) o consuma più di un gigabyte di RAM, la soluzione non sarà valida. Questi limiti sono volutamente lassisti, per favore cerca di non spingerli. (Vedi emendamento di seguito: devi essere in grado di gestire almeno 100 kB di input entro questi limiti.)

  • Anche se solo l'input di test è importante per il punteggio, dovresti almeno fare uno sforzo per comprimere il testo di input arbitrario. Una soluzione che raggiunge un rapporto di compressione decente solo per l'input del test, e per nient'altro, è tecnicamente valida ma non otterrà un mio voto.

  • I programmi del compressore e del decompressore devono essere autonomi . In particolare, se dipendono dalla capacità di leggere alcuni file o risorse di rete che non fanno parte dell'ambiente di runtime standard della lingua prescelta, la lunghezza di tale file o risorsa deve essere conteggiata come parte della lunghezza dei programmi. (Questo per impedire ai "compressori" che confrontano l'input con un file sul web e che producono zero byte se corrispondono. Mi dispiace, ma non è più un nuovo trucco.)

Emendamenti e chiarimenti:

  • Il compressore deve essere in grado di gestire file costituiti da almeno 100 kB di testo tipico inglese entro un tempo ragionevole e l'utilizzo della memoria (al massimo un minuto e un GB di memoria). Il decompressore deve essere in grado di decomprimere l'output risultante entro gli stessi limiti. Naturalmente, essere in grado di gestire i file più a lungo di quello è perfettamente bene e lodevole. Va bene suddividere i file di input lunghi in blocchi e comprimerli singolarmente o utilizzare altri mezzi per compromettere l'efficienza di compressione per la velocità per input lunghi.

  • Il compressore potrebbe richiedere che venga fornito il proprio input utilizzando la rappresentazione newline nativa della piattaforma preferita (LF, CR + LF, CR, ecc.), Purché il decompressore utilizzi la stessa rappresentazione newline nel proprio output. Naturalmente, va anche bene che il compressore accetti qualsiasi tipo di newline (o anche solo newline Unix indipendentemente dalla piattaforma), purché il tuo decompressore emetta lo stesso tipo di newline dell'input originale.

Test input:

Per valutare l'efficienza di compressione delle risposte, verrà utilizzato il seguente input di test ( The Raven di Edgar Allan Poe, per gentile concessione del Progetto 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!

L'input di test corretto (codificato con newline LF in stile Unix) dovrebbe essere lungo 7043 byte e avere l'hash MD5 esadecimale 286206abbb7eca7b1ab69ea4b81da227. ( md5sum -tdovrebbe produrre lo stesso valore di hash anche se si utilizzano newline CR + LF su DOS / Windows.) L'output del decompressore dovrebbe avere la stessa lunghezza e hash.

Ps. Tieni presente che questa sfida è difficile tanto quanto la fai. Davvero, qualsiasi cosa sotto 7043 conta come un buon punteggio. (All'altra estremità della scala, rimarrò estremamente colpito se qualcuno raggiungerà un punteggio inferiore a 2500.)


Quindi suppongo che tu non voglia vedere alcuna compressione con perdita ?
Mr. Llama,

2
Nota preventiva per le persone che non riescono a far corrispondere l'hash MD5: il file di testo ha newline Unix per i terminatori di riga. Inoltre, assicurati di avere l'ultima riga finale nel file per l'intera lunghezza di 7043 byte.
Mr. Llama,

@GigaWatt: Sì, avrei dovuto essere più esplicito sulle nuove linee. Dal momento che ho limitato l'input solo al testo ASCII, suppongo che potrei consentire alle persone di usare qualsiasi convenzione newline più naturale per loro, purché lo usino in modo coerente. Proverò a pensare a un bel modo di esprimerlo nella sfida. E no, il compressore non dovrebbe essere in perdita.
Ilmari Karonen,

Che ne dici della lunghezza dei file, è necessario eseguirli (in tempi accettabili) solo per i file nell'ordine delle dimensioni dell'esempio o anche per file molto più grandi (> alcuni MB)?
cessò di girare in senso antiorario il

1
Se l'output viene fornito come programma nella stessa lingua del compressore, possiamo contare come zero la lunghezza del decompressore?
Peter Taylor,

Risposte:


19

Perl, 3502 = 133 + 3269 + 100

L'encoder:

#!/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

E il decodificatore:

#!/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

Per i puristi che preferiscono evitare di utilizzare le opzioni della riga di comando: è possibile rimuovere la linea shebang e aggiungerla $/=chr;all'encoder e $/=$,;al decoder per ottenere lo stesso effetto. (Ciò porterebbe il punteggio fino a 3510.)

Questo codice utilizza uno schema di compressione molto primitivo:

  • Trova il bigram a due caratteri che appare più frequentemente nel testo di origine.
  • Sostituisci il bigram con un valore byte attualmente inutilizzato.
  • Ripetere fino a quando non ci sono più bigram ripetuti (o non più valori di byte inutilizzati).

Qualcuno là fuori potrebbe riconoscerlo come una versione semplificata della compressione "re-pair" (abbreviazione di coppie ricorsive).

Non è un ottimo schema di compressione generale. Funziona bene solo con cose come il testo ASCII, dove ci sono molti valori di byte inutilizzati e anche in questo caso in genere non ottiene un rapporto superiore al 45-50%. Tuttavia, ha il vantaggio di essere implementabile con un minimo di codice. Il decompressore in particolare può essere abbastanza compatto. (La maggior parte dei caratteri nel mio script di decodifica serve per recuperare il dizionario bigram.)

Ecco una versione non modificata del codice:

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

Un'espressione nell'encoder golfato richiede una spiegazione, penso, e cioè (sort{$p{$a}<=>$p{$b}}keys%p)[-1], per ottenere la chiave con il valore più alto. Sembra che dovrebbe essere scritto come (sort{$p{$b}<=>$p{$a}}keys%p)[0], il che fa la stessa cosa ed è più corto di un personaggio. Il motivo per cui non l'ho scritto in questo modo è che altera la chiave selezionata nel caso in cui ci siano più chiavi con il valore più alto. Per puro caso, ciò ha comportato che l'output risultante per l'input del test fosse più lungo di 10 byte. Odiavo assumere l'inutile personaggio in più, ma non abbastanza da sacrificare 9 punti dal mio punteggio.

In faccia, Golfscript! (Haha, Golfscript verrebbe totalmente qui e mi darebbe un calcio nel culo se potesse sentirmi.)


3
Wow, è davvero impressionante! Ps. Questa sembra essere la risposta generalmente accettata per quanto riguarda il conteggio delle opzioni della riga di comando.
Ilmari Karonen,

Dang, l'ho letto prima ma non ho notato quel pezzo nel mezzo. Sembra che il risultato sia questo: non conteggi il carattere trattino iniziale (perché puoi semplicemente aggiungerlo al -epacchetto di opzioni), a meno che il tuo codice non contenga un carattere a virgoletta singola, nel qual caso conti il ​​trattino (perché ora devi eseguirlo da un file con una riga shebang per evitare di pagare per sfuggire alla virgoletta singola sulla riga di comando).
breadbox,

1
La tecnica è anche chiamata codifica di coppie di byte . Bella implementazione
roblogic

@roblogic Grazie per il riferimento; buono a sapersi.
breadbox

20

Python, 3514 = 294 + 2894 + 326

Fondamentalmente un'implementazione di bzip2 . Esegue una trasformazione Burrows-Wheeler , una trasformazione sposta in primo piano , una semplice codifica Huffman in un flusso di bit, converte quel flusso di bit in un numero intero e scrive i byte.

Codificatore:

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è la coda di spostamento in primo piano, Hè l'encoder Huffman ed Nè il bitstream.

La codifica riduce l'input di prova a circa il 41% delle sue dimensioni originali.

decoder:

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
Sono stato tentato di implementare il BWT e fare una vera forma di compressione ma sono diventato troppo pigro. : P
Mr. Llama,

8

8086 Assembler / MS_DOS

Compressore: 155

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

Dati: 3506

Decompressore: 203

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

Totale: 3864

Usa questo decodificatore Base64 e salva i file binari come 'compress.com' e 'decompress.com' e poi fai:

compress < source > compressed_file
decompress < compressed_file > copy_of_source

in una shell DOS (testato con WinXP). Non si verifica alcun errore, quindi la compressione di file di grandi dimensioni creerà risultati errati. Alcune piccole aggiunte e potrebbe far fronte a qualsiasi file di dimensioni. Inoltre, non può decomprimersi in binario in quanto non può generare un valore 0xff (i dati compressi sfuggono al valore 0xff come 0xfe 0xff con 0xfe scappato come 0xfe 0xfe). L'uso dei nomi dei file della riga di comando supererebbe il problema di output binario, ma sarebbe un eseguibile più grande.


Che tipo di algoritmi di compressione utilizza il programma?
Sir_Lagsalot,

@Sir_Lagsalot: utilizza una larghezza di bit variabile LZW (quella utilizzata nei file GIF).
Skizz,

6

Bash Poem (566 + 117) + 4687 = 5370

Per divertimento ho mascherato un compressore come una poesia:

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

Questo è un compressore unificato: eseguito con l'opzione "c" comprime e con "d" si decomprime. Ha due parti: una versione "digest digest" di 566 byte del poema e (2) un suffisso di 117 byte in cui viene eseguita tutta la bash "reale".

Con un po 'di attenzione (es. Iniziare la poesia con "for I in") bash interpreterà la versione "lossy" della poesia come un array. Sostituisce ogni elemento dell'array con un carattere non ASCII (supponiamo che l'input sia ASCII, quindi non ci sono collisioni). Un vantaggio minore di questa soluzione: poiché utilizziamo il fatto che possiamo supporre che l'input sia ASCII, l'output di questa compressione non sarà mai più lungo del suo input, indipendentemente da quale sia l'input e / o la parte con perdita.

La regola che si avvicina di più alla violazione è la regola di fornire un rapporto di compressione decente su altri testi. Tuttavia, elimina 1386 byte dal testo GPL V2, ben oltre la sua stessa dimensione, che sembra corrispondere alla definizione di OP di decent. Sembra quindi fornire la cosiddetta decentcompressione sui testi generali. Questo perché praticamente qualsiasi testo inglese avrà "il" "che" ecc. Chiaramente funzionerà meglio se si sostituisce la parte "lossy" con testo simile all'originale che si desidera comprimere senza perdita.

La divisione di immagini e audio in parti con perdita e non con perdita è una tecnica nota. Questo non funziona altrettanto bene per il testo: 4687 byte non è eccezionale anche se escludiamo i 566 byte dalla versione con perdita e non possiamo davvero generare automaticamente una versione con perdita di testo come per l'audio. Tra i lati positivi, ciò significa che ogni volta che comprimi qualcosa con questo compressore, puoi divertirti a creare una versione con perdita manuale. Quindi questa sembra una ragionevole soluzione "per divertimento".


5

C ++, 4134 byte (codice = 1357, compresso = 2777)

Questo trasforma una trasformazione di Burrows-Wheeler + un Move-To-Front come quello di Keith Randall, ma poi comprime la sequenza di byte risultante usando un Range Coder adattivo . Sfortunatamente, la compressione migliorata dal codificatore di intervallo non è sufficiente per compensare la verbosità di C ++. Potrei golf questo codice un po 'di più, vale a dire utilizzare un metodo di input / output diverso, ma non sarebbe sufficiente per battere gli altri invii con l'algoritmo corrente. Il codice è specifico di Windows e è supportato solo il testo ASCII.
Per comprimere: "C text_file compressed_file"
Per decomprimere: "D compressed_file uncompressed_file"
Praticamente qualsiasi errore della riga di comando o errore del file si arresta in modo anomalo al programma e impiega la parte migliore di un minuto per codificare o decodificare la poesia.

#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 (codice) + 3521 (test) = 3914 (totale)

Questo programma sostituisce iterativamente valori di byte non utilizzati per blocchi da 2 a 4 caratteri dell'input. Ogni sostituzione viene assegnata in base alla frequenza e alla lunghezza del pezzo originale e ogni volta viene scelta la migliore sostituzione. Aggiungerei una fase finale di programmazione di Huffman se riuscissi a capire come farlo in un numero relativamente piccolo di personaggi. La decompressione è essenzialmente una serie di operazioni di ricerca e sostituzione.

uso

C () fornisce compressione; U () fornisce decompressione. Poiché le stringhe di JavaScript si basano su unità di codice Unicode a 16 bit, nel formato di dati compressi vengono utilizzati solo gli 8 bit meno significativi di ciascuna unità di codice; questo è compatibile con le funzioni btoa () e atob () di Firefox per la codifica Base64. ( esempio di utilizzo )

Questo programma può funzionare solo in Firefox a causa dell'opzione "g" non standard di .replace ().

Codice

Codice golfizzato:

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}

Prima di giocare a golf:

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;
}

Perché ottengo C(text).length=7301? (FF 60.0.2)
l4m2

3

PHP, (347 + 6166 + 176) = 6689

Quindi sono andato con un dizionario semplicistico + approccio di sostituzione.

Se una parola appare più volte ed è più breve (codifica la parola + salva la voce di sostituzione), allora effettua la sostituzione. Se la "parola" sembra essere un numero, lo fa comunque per evitare sostituzioni accidentali durante la decompressione. Il "dizionario" delle sostituzioni è unito da byte nulli, seguito da due byte nulli, seguito dal corpo su cui lavora la sostituzione.

Possibili miglioramenti:

  • A Windows non piace eseguire il piping di oltre 4 KB di dati, quindi trova un modo migliore rispetto all'utilizzo dei file.
  • La capacità di abbinare lunghe stringhe di spazi bianchi e contarle come "parole" senza aggiungere troppo codice.
  • Trovando qualcosa di meglio sostituzioni invece di usare i numeri.

Uso: il compressore cerca un file chiamato "i" e scrive i dati compressi su "o". Il decompressore cerca "o" e scrive i dati non compressi in "d". Questa è la mia sciagurata soluzione a Windows che non preferisce diffondere file di dati.


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);

Versione estesa con commenti e spiegazioni.


Esempio di output senza dizionario. Un po 'divertente da guardare.
Dimensioni normali: 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);

Versione estesa con spiegazione.


Eventuali suggerimenti per il miglioramento sono benvenuti.

Modifica: aggiunte le versioni "srotolate" del codice e aggiunte tonnellate di commenti. Dovrebbe essere facile da seguire.


Gah! Stesso linguaggio e metodo che stavo usando! Dannazione. Anche se non ero arrivato al punto di saltare singole parole.
Gareth,

cosa succede quando ci sono numeri all'interno del testo? finirebbe per sostituire i numeri originali con una parola fuori posto. Sebbene abbia adottato un approccio simile (regex si divide, trovando parole comuni per sostituire e produrre un dizionario sostitutivo e incollandolo con valori nulli), ho usato caratteri unicode invece di numeri (a partire da chr (128), poiché qualsiasi cosa successiva non è stampabile in ascii standard)
Blazer il

@Blazer: In realtà, esiste un codice (vale ||is_int($w)a dire ) per gestire i numeri aggiungendoli sempre al dizionario, ma sembra essere difettoso: dopo aver compresso e decompresso l' intero E-text di Gutenberg, l'output inizia con The 4 3 EBook 2 The Raven, by Edgar Allan Poe. :-( Sospetto che il problema sia che qualcosa viene sostituito due volte; potresti voler prendere in considerazione l'uso strtr()invece di evitare quel problema.
Ilmari Karonen,

@Ilmari se hai un documento con molti numeri, l'aggiunta di quei numeri al dizionario potrebbe comportare una compressione maggiore di quanto non fosse in origine. conservare diversi oggetti lunghi 1-2 caratteri non è efficace. come se dovessi sostituire la parola "a" nel documento
Blazer il

@Blazer - Per tutti gli algoritmi di compressione ci sono alcuni input che risulteranno in un output più grande . È inerente alla compressione senza perdita, proprio come l'incapacità di comprimere i dati entropici in modo affidabile.
Mr. Llama,

3

GolfScript, 3647 (dimensione compressa 3408 + dimensione codice 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

L'algoritmo utilizzato è la compressione LZW con codici a larghezza variabile. La prima riga è il codice condiviso, la seconda è il codice di compressione e la terza è il codice di decompressione.

Gestisce file con caratteri ASCII nell'intervallo 1-127 e riconosce automaticamente i file compressi (iniziano con uno 0 byte), quindi non ci sono parametri necessari per decomprimere.

Esempio di esecuzione:

$ 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  -

Nota: ho iniziato così a lungo prima che fosse aggiunto il requisito di gestire 100kb, quindi non l'ho testato su input di quella dimensione. Tuttavia, sono necessari circa 30 secondi per comprimere l'ingresso di test e 5 secondi per decomprimerlo, utilizzando circa 20 MB di memoria al suo picco.


La compressione di un file 76 kB sembra prendere circa 19 minuti, mentre la decompressione prende 10. Questo è un po 'lento, ma poi di nuovo, lo fa passare le regole originali, quindi ... non so. Sembra un po 'ingiusto non permetterlo nelle circostanze. Immagino di poter invocare una "clausola del nonno" implicita per te o qualcosa del genere.
Ilmari Karonen,

3

Haskell, 3973

In ritardo alla festa, e non ho intenzione di vincere, ma mi sono divertito a scriverlo, quindi potrei anche pubblicarlo.

È una semplice implementazione a larghezza variabile di LZW, con un dizionario esplicitamente limitato a ASCII, tab e linefeed stampabili. Eseguito senza argomenti, comprime l'input standard in file C. Esegui con qualsiasi argomento (ma "--decompress" sarebbe una scommessa ragionevole), decomprime il file Cnell'output standard.

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)
  • codice: 578
  • dimensione del campione compresso: 3395
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.