Rhabarberbarbara, Barbarabar, Barbarbarbier: Wo sich etwas wiederholt, lässt es sich kürzer sagen. LZW merkt sich beim Lesen jede Folge von Bytes, die schon einmal vorkam, und schreibt beim nächsten Mal nur noch eine Nummer. Das Wörterbuch dafür wird nie mitgeschickt: Wer entpackt, baut es beim Lesen selbst noch einmal. Du schreibst beides selbst, in Ruby oder Python, den Kompressor und den Entpacker, und probierst sie an Texten, Fotos und der Spezifikation von GIF aus.
Ein Text wiederholt sich ständig: Buchstabenfolgen wie »ch«, »sch«, »ein« und »der« kommen auf jeder Seite dutzendfach vor, ganze Wörter auch. Ein Programm, das komprimiert, sucht solche Wiederholungen und ersetzt sie durch etwas Kürzeres. Beim Entpacken muss genau das Original zurückkommen, Byte für Byte; das heißt verlustfrei.
Das Verfahren auf dieser Seite haben Abraham Lempel und Jacob Ziv 1978 erfunden und Terry Welch 1984 so vereinfacht, wie es seitdem benutzt wird, daher der Name LZW. Es steckt in GIF-Bildern, in TIFF, in alten PDF-Dateien und im Unix-Programm compress. Die Idee ist ein Wörterbuch, in dem jede Folge von Bytes, die schon vorkam, eine Nummer hat, den Code:
Der Kompressor liest den Text Byte für Byte und merkt sich das Präfix, das er gerade aufbaut: den Anfang des restlichen Texts, der schon im Wörterbuch steht. Für jedes Byte gilt:
Probier es aus. Jeder Klick liest ein Byte; das gelbe ist das Präfix, das rot umrandete das nächste Byte. Du kannst auch einen eigenen Text eintippen.
Bei der Rhabarberbarbara stehen nach dem ersten »BAR« für »BA«, »AR« und »RB« eigene Einträge im Wörterbuch, und schon das zweite »BARBARA« braucht weniger Codes als Buchstaben. Ganz am Anfang kann LZW nichts sparen: Das Wörterbuch kennt noch nichts außer den einzelnen Bytes.
Komprimiere ABABABA von Hand, nach den drei Regeln, bevor du es oben
in den Schrittmacher tippst. »A« ist das Byte 65, »B« das Byte 66.
Welche Codes kommen heraus? Schreib sie mit Leerzeichen dazwischen,
dann sieh im Schrittmacher nach.
Schreib eine Tabelle mit den Spalten Präfix, Byte, Ausgabe und neuer Eintrag. Der erste neue Eintrag ist 256 = AB. Mit dem Schrittmacher kannst du danach nachsehen.
Lege im Terminal (StrgJ) einen Ordner an und öffne ihn mit StrgK StrgO:
mkdir /workspace/lzw
Lege darin die Datei lzw.rb an:
# LZW: komprimieren und entpacken # Macht aus den Bytes eines Strings eine Liste von Codes. def compress(data) codes = [] codes end # Macht aus einer Liste von Codes wieder die Bytes, als String. def decompress(codes) '' end p compress('ABABABA')
Ein String besteht aus Bytes, Zahlen von 0 bis 255: 'ABABABA' sind die Bytes 65, 66, 65, … Was unten außerhalb der Methoden steht, ist dein Programm: Es läuft, wenn du die Datei startest, mit ruby lzw.rb. Die Prüfungen der Aufgaben laden nur deine Methoden und rufen sie direkt auf; dein Programm läuft dabei nicht. Im Moment gibt das Programm eine leere Liste aus; das änderst du jetzt.
Lege darin die Datei lzw.py an:
# LZW: komprimieren und entpacken def compress(data): """Macht aus Bytes eine Liste von Codes.""" codes = [] return codes def decompress(codes): """Macht aus einer Liste von Codes wieder die Bytes.""" return b'' print(compress(b'ABABABA'))
b'ABABABA' ist kein Text, sondern eine Folge von Bytes, Zahlen von 0 bis 255. Was unten außerhalb der Funktionen steht, ist dein Programm: Es läuft, wenn du die Datei startest, mit python3 lzw.py. Die Prüfungen der Aufgaben laden nur deine Funktionen und rufen sie direkt auf; dein Programm läuft dabei nicht. Im Moment gibt das Programm eine leere Liste aus; das änderst du jetzt.
Schreib compress nach den drei Regeln oben. Was du aus Ruby dafür brauchst:
data.each_byte { |byte| … } geht die Bytes eines Strings durch und liefert Zahlen: für 'AB' erst 65, dann 66.[65, 66] für »AB«. current + [byte] ist ein neues Array mit einer Zahl mehr.i von 0 bis 255 den Eintrag [i] mit dem Code i. Der nächste freie Code ist dann immer table.size, und table.key?(prefix) sagt, ob es ein Präfix prefix schon gibt.[]. Achte am Ende darauf, dass ein leerer Text keinen Code ergibt.Schreib compress nach den drei Regeln oben. Was du aus Python dafür brauchst:
for-Schleife über Bytes liefert Zahlen: for byte in b'AB' gibt erst 65, dann 66.bytes([byte]) wieder ein Byte, und + hängt Bytes aneinander: b'AB' + bytes([67]) ist b'ABC'.dict mit Bytes als Schlüsseln und Codes als Werten. Am Anfang enthält es für jede Zahl i von 0 bis 255 den Eintrag bytes([i]) mit dem Code i. Der nächste freie Code ist dann immer len(table).b''. Achte am Ende darauf, dass ein leerer Text keinen Code ergibt.Schreib compress in lzw.rb.
Das Wörterbuch ist am Anfang 256.times { |i| table[[i]] = i }, mit
table = {} davor. Ein Eintrag kommt immer dann dazu, wenn ein Code
ausgegeben wird, und zwar das Präfix mit dem neuen Byte dahinter.
Schreib compress in lzw.py.
Das Wörterbuch ist am Anfang {bytes([i]): i for i in range(256)}. Ein
Eintrag kommt immer dann dazu, wenn ein Code ausgegeben wird, und zwar
das Präfix mit dem neuen Byte dahinter.
Der Entpacker bekommt nur die Codes. Die Codes unter 256 versteht er sofort, sie sind einzelne Bytes. Für alle anderen baut er das Wörterbuch selbst nach, so wie der Kompressor es gebaut hat, nur einen Schritt später:
Das Wörterbuch des Entpackers ist also immer einen Eintrag hinter dem des Kompressors. Meistens macht das nichts, aber nicht immer:
Der Entpacker bekommt 65 66 256 258. Nach 65, 66 und 256 kennt
er die Einträge bis 257. Wofür steht 258?
Der Kompressor kann einen Eintrag benutzen, den er gerade erst angelegt hat. Das passiert, wenn sich ein Präfix sofort wiederholt und dabei mit seinem eigenen ersten Byte weitergeht, wie bei ABABABA. Der Entpacker kennt diesen Eintrag noch nicht, aber er kann ihn ausrechnen: Es ist der vorige Eintrag mit dessen eigenem ersten Byte dahinter. Das ist die einzige Ausnahme von Regel 2.
Hier entpackt der Schrittmacher die Codes des Texts, der oben im Feld steht; die Ausnahme ist rot markiert. Tipp oben ABABABA oder AAAAAAAA ein und sieh hier zu.
Schreib jetzt decompress. Diesmal ist das Wörterbuch andersherum: ein Hash mit den Codes als Schlüsseln und den Einträgen, Arrays von Zahlen, als Werten. Sammle alle Zahlen in einem Array; entry[0] ist das erste Byte eines Eintrags. Am Schluss macht pack('C*') aus dem Array von Zahlen wieder einen String: [65, 66].pack('C*') ist 'AB'.
Schreib jetzt decompress. Diesmal ist das Wörterbuch andersherum: ein dict mit den Codes als Schlüsseln und den Bytes als Werten. entry[:1] ist das erste Byte eines Eintrags, als Bytes (entry[0] wäre eine Zahl). Am Schluss setzt b''.join(parts) eine Liste von Bytes wieder zu einer einzigen Folge zusammen.
Schreib decompress in lzw.rb. Was compress macht, muss
decompress genau zurückgeben, für jede Folge von Bytes.
Merk dir den vorigen Eintrag. Steht ein Code noch nicht im Wörterbuch,
ist er genau table.size, und sein Eintrag ist der vorige mit dessen
erstem Byte dahinter: previous + [previous[0]].
Schreib decompress in lzw.py. Was compress macht, muss
decompress genau zurückgeben, für jede Folge von Bytes.
Merk dir den vorigen Eintrag. Steht ein Code noch nicht im Wörterbuch, ist er genau len(table), und sein Eintrag ist der vorige mit dessen erstem Byte dahinter. entry[:1] ist das erste Byte als Bytes, entry[0] wäre eine Zahl.
Bisher zählen wir Codes. Gespeichert werden muss aber jeder Code mit so vielen Bits, wie er braucht, und die Codes werden immer größer. Beim i-ten Code (ab 0 gezählt) hat das Wörterbuch 256 + i Einträge, der größte mögliche Code ist also 255 + i. So viele Bits braucht der Code: In Ruby sagt das (255 + i).bit_length. Schreib eine Funktion size_in_bytes(codes), die alle Bits zusammenzählt und in Bytes umrechnet, aufgerundet.
Bisher zählen wir Codes. Gespeichert werden muss aber jeder Code mit so vielen Bits, wie er braucht, und die Codes werden immer größer. Beim i-ten Code (ab 0 gezählt) hat das Wörterbuch 256 + i Einträge, der größte mögliche Code ist also 255 + i. So viele Bits braucht der Code: In Python sagt das (255 + i).bit_length(). Schreib eine Funktion size_in_bytes(codes), die alle Bits zusammenzählt und in Bytes umrechnet, aufgerundet.
Lade dann zwei Dateien zum Ausprobieren in den Ordner: die Spezifikation von GIF, ein Text von 1990 mit 83.003 Bytes, und ein Foto als JPEG.
wget https://workspace.hackschule.de/lzw/spec-gif89a.txt wget https://workspace.hackschule.de/jpeg/44-baseline.jpg
Eine Datei liest Ruby mit File.binread('spec-gif89a.txt') Byte für Byte, so wie sie ist. ARGV[0] ist der Name, den du beim Start mitgibst: ruby lzw.rb spec-gif89a.txt. Lass dein Programm für eine Datei ausgeben, wie groß sie ist, wie viele Codes compress daraus macht und wie viele Bytes die brauchen. Probier dabei auch gleich, ob decompress wieder genau die Datei ergibt.
Eine Datei liest Python mit open('spec-gif89a.txt', 'rb').read() als Bytes; das b in 'rb' ist wichtig, sonst bekommst du Text statt Bytes. sys.argv[1] ist der Name, den du beim Start mitgibst (python3 lzw.py spec-gif89a.txt), nach import sys. Lass dein Programm für eine Datei ausgeben, wie groß sie ist, wie viele Codes compress daraus macht und wie viele Bytes die brauchen. Probier dabei auch gleich, ob decompress wieder genau die Datei ergibt.
Wie viele Bytes braucht spec-gif89a.txt nach size_in_bytes, also
komprimiert, mit so vielen Bits pro Code wie nötig?
(255 + i).bit_length Bits für den i-ten Code, alle zusammenzählen, durch 8 teilen und aufrunden. Lies die Datei mit File.binread.
Wie viele Bytes braucht spec-gif89a.txt nach size_in_bytes, also
komprimiert, mit so vielen Bits pro Code wie nötig?
(255 + i).bit_length() Bits für den i-ten Code, alle zusammenzählen, durch 8 teilen und aufrunden. Lies die Datei mit open('spec-gif89a.txt', 'rb').read().
Echte Programme komprimieren noch besser. gzip benutzt LZ77, die erste Idee von Lempel und Ziv von 1977, und codiert das Ergebnis danach noch mit Huffman:
gzip -9 -k spec-gif89a.txt ls -l spec-gif89a.txt*
spec-gif89a.txt |
Bytes |
|---|---|
| Original | 83.003 |
| LZW, mit wachsender Breite der Codes | dein Ergebnis |
gzip -9 |
20.271 |
Und das Foto?
Was passiert, wenn du 44-baseline.jpg (143.206 Bytes) mit deinem
LZW komprimierst? Rate erst, dann probier es aus.
Warum wird das JPEG mit LZW größer statt kleiner? Erkläre es in eigenen Worten. Was müsste in einer Datei stehen, damit LZW viel spart?
Unser Wörterbuch wächst ohne Grenze: Bei der Spezifikation hat es am Ende über 17.000 Einträge, bei einer großen Datei wären es Millionen. Echte Programme setzen eine Grenze. GIF erlaubt höchstens 4096 Einträge, also Codes mit höchstens 12 Bits; compress geht bis 16 Bits. Ist das Wörterbuch voll, kommen keine Einträge mehr dazu, oder es beginnt von vorn: Der Kompressor schickt dafür einen eigenen Code, den Clear Code, und beide Seiten fangen wieder mit den 256 Bytes an.
Bau das in deine beiden Funktionen ein: Reserviere den Code 256 als Clear Code, die neuen Einträge beginnen dann bei 257. Hat das Wörterbuch 4096 Einträge, gibt compress den Clear Code aus, und beide Seiten beginnen neu. Vergleich mit size_in_bytes, wie viel größer die Spezifikation dadurch wird, und überleg, warum ein frisches Wörterbuch bei einem langen Text sogar helfen kann.
head -c 100000 /dev/urandom > zufall.bin). Wann lohnt sich LZW, wann nicht?