Im Morsecode ist E ein einziger kurzer Ton und Q ein langes Lang-lang-kurz-lang, weil E so viel häufiger vorkommt. Huffman-Codes machen dasselbe mit Bytes, aber so, dass man keine Pausen braucht, um zu wissen, wo ein Code aufhört. Du baust den Baum, aus dem die Codes kommen, in Ruby oder Python, codierst und decodierst damit und findest heraus, wie JPEG seine Huffman-Tabellen mit ein paar Bytes speichert.
Jedes Byte hat 8 Bits, ob es ein »e« ist, in einem deutschen Text etwa jeder sechste Buchstabe, oder ein »q«, das kaum vorkommt. Das ist bequem, aber verschwenderisch. Samuel Morse und Alfred Vail haben das schon in den 1830er Jahren besser gemacht: Im Morsecode ist E ein einziger kurzer Ton (·), T ein langer (−), und seltene Buchstaben wie Q (−−·−) sind lang. Im Mittel wird eine Nachricht so kürzer.
Der Morsecode braucht dafür Pausen zwischen den Buchstaben: Ohne sie könnte ·· ein I sein oder zweimal E. Ein Computer soll ohne Pausen auskommen. Das geht, wenn kein Code der Anfang eines anderen ist. Dann heißt die Codetabelle präfixfrei: Wer Bit für Bit liest, weiß in dem Moment, in dem ein Code vollständig ist, dass er zu Ende ist.
| Zeichen | Code |
|---|---|
| A | 0 |
| B | 10 |
| R | 110 |
| C | 1110 |
| D | 1111 |
Mit dieser Tabelle ist 0101100 eindeutig: 0 ist A, dann 10 ist B, 110 ist R, 0 ist A. Kein Code beginnt mit einem anderen, also gibt es keine zweite Möglichkeit.
Welche dieser Tabellen ist präfixfrei, lässt sich also ohne Pausen eindeutig lesen?
Wie findet man die besten Codes, also die, mit denen ein Text so wenige Bits wie möglich braucht? Diese Frage stellte Robert Fano 1951 seinen Studierenden am MIT als Hausarbeit, statt einer Prüfung. Er selbst kannte nur ein Verfahren, das gute, aber nicht immer die besten Codes findet. David Huffman fand nach Monaten eins, das immer die besten findet, und es ist erstaunlich einfach:
Den Code eines Bytes liest du auf dem Weg von oben zu seinem Blatt ab: links eine 0, rechts eine 1. Seltene Bytes werden früh zusammengefasst und landen tief unten, mit langen Codes; häufige kommen spät dazu und bleiben oben, mit kurzen. Und weil jedes Byte an einem Blatt hängt und nie auf dem Weg zu einem anderen liegt, ist die Tabelle von selbst präfixfrei.
Probier es aus, mit dem Text unten oder einem eigenen. Rot umrandet sind die beiden Bäume, die als nächste zusammenkommen.
Haben zwei Bäume dasselbe Gewicht, ist es egal, welchen man nimmt. Die Codes sehen dann anders aus, manche sind sogar anders lang, aber der Text braucht insgesamt genau gleich viele Bits.
Bau den Huffman-Baum für ABRACADABRA auf Papier, bevor du ihn oben im
Schrittmacher nachbaust: A kommt 5-mal vor, B und R je 2-mal, C und D
je einmal. Wie viele Bits braucht der ganze Text mit diesen Codes?
Fass zuerst C und D zusammen (Gewicht 2). Dann gibt es drei Bäume mit Gewicht 2. Am Ende zählst du für jedes Zeichen Anzahl mal Länge seines Codes zusammen.
Lege im Terminal (StrgJ) einen Ordner an und öffne ihn mit StrgK StrgO:
mkdir /workspace/huffman
Lege darin die Datei huffman.rb an:
# Huffman-Codes: häufige Bytes bekommen kurze Codes, seltene lange. # # Ein Baum ist entweder ein Blatt, das Byte selbst (eine Zahl), oder ein # Knoten, ein Array [links, rechts]. Links steht für 0, rechts für 1. # Wie oft jedes Byte vorkommt: {65 => 5, 66 => 2, ...} def frequencies(data) end # Immer die beiden leichtesten Bäume zusammenfassen, bis einer übrig ist. def build_tree(counts) end # Der Code jedes Bytes als String aus 0 und 1: {65 => '0', 66 => '10', ...} def code_table(tree) end # Der ganze Text als String aus 0 und 1 def encode(data, table) end # Aus den Bits und dem Baum wieder die Bytes def decode(bits, tree) end p frequencies('ABRACADABRA')
Lege darin die Datei huffman.py an:
# Huffman-Codes: häufige Bytes bekommen kurze Codes, seltene lange. # # Ein Baum ist entweder ein Blatt, das Byte selbst (eine Zahl), oder ein # Knoten, ein Tupel (links, rechts). Links steht für 0, rechts für 1. def frequencies(data): """Wie oft jedes Byte vorkommt: {65: 5, 66: 2, ...}""" def build_tree(counts): """Immer die beiden leichtesten Bäume zusammenfassen, bis einer übrig ist.""" def code_table(tree): """Der Code jedes Bytes als String aus 0 und 1: {65: '0', 66: '10', ...}""" def encode(data, table): """Der ganze Text als String aus 0 und 1""" def decode(bits, tree): """Aus den Bits und dem Baum wieder die Bytes""" print(frequencies(b'ABRACADABRA'))
b'ABRACADABRA' sind Bytes, keine Zeichen; alle Methoden bekommen und liefern Bytes.
Die Bits schreiben wir der Einfachheit halber als String aus den Zeichen 0 und 1. Echte Programme packen je 8 davon in ein Byte, wie GIF und JPEG es tun; gezählt wird hier trotzdem in Bits.
Schreib frequencies. Es gibt einen Hash zurück, der für jedes Byte des Texts sagt, wie oft es vorkommt.
Schreib frequencies. Es gibt ein dict zurück, das für jedes Byte des Texts sagt, wie oft es vorkommt.
data.each_byte { |byte| … } geht die Bytes durch, als Zahlen.Hash.new(0) ist ein Hash, der für jeden Schlüssel, den es noch nicht gibt, 0 liefert. Damit kannst du gleich counts[byte] += 1 schreiben.for byte in data: geht die Bytes durch, als Zahlen.counts.get(byte, 0) liefert 0, wenn es das Byte noch nicht gibt. Damit kannst du gleich counts[byte] = counts.get(byte, 0) + 1 schreiben.Schreib frequencies in huffman.rb.
Mit counts = Hash.new(0) liefert counts[byte] für jedes neue Byte 0, und counts[byte] += 1 zählt.
Schreib frequencies in huffman.py.
Mit counts[byte] = counts.get(byte, 0) + 1 zählst du, auch wenn es das Byte noch nicht gibt.
Schreib build_tree nach den drei Schritten von oben. Ein Blatt ist das Byte selbst, eine Zahl; ein Knoten ist ein Array aus seinen beiden Kindern. Für »ABRACADABRA« könnte der Baum so aussehen: [65, [[67, 68], [66, 82]]].
Schreib build_tree nach den drei Schritten von oben. Ein Blatt ist das Byte selbst, eine Zahl; ein Knoten ist ein Tupel aus seinen beiden Kindern. Für »ABRACADABRA« könnte der Baum so aussehen: (65, ((67, 68), (66, 82))).
[weight, tree], am Anfang eines für jedes Byte.sort_by! { |weight, _| weight } sortiert die Liste nach dem Gewicht, shift nimmt das erste Element heraus. Zweimal shift nach dem Sortieren gibt dir die beiden leichtesten.(weight, tree), am Anfang eines für jedes Byte.trees.sort(key=lambda pair: pair[0]) sortiert die Liste nach dem Gewicht, trees.pop(0) nimmt das erste Element heraus. Zweimal pop(0) nach dem Sortieren gibt dir die beiden leichtesten.Dann code_table: Es geht den Baum von oben durch und merkt sich den Weg. Am einfachsten ist das mit einer Methode, die sich selbst aufruft, für das linke Kind mit einer 0 mehr im Weg, für das rechte mit einer 1. Kommt sie an ein Blatt, also an eine Zahl (tree.is_a?(Integer)), ist der Weg bis dorthin der Code dieses Bytes. Eine Methode darf dafür weitere Parameter mit Standardwerten haben, etwa def code_table(tree, prefix = '', table = {}).
Dann code_table: Es geht den Baum von oben durch und merkt sich den Weg. Am einfachsten ist das mit einer Funktion, die sich selbst aufruft, für das linke Kind mit einer 0 mehr im Weg, für das rechte mit einer 1. Kommt sie an ein Blatt, also an eine Zahl (isinstance(tree, int)), ist der Weg bis dorthin der Code dieses Bytes. Eine Funktion darf dafür weitere Parameter mit Standardwerten haben, etwa def code_table(tree, prefix=''); table.update(…) nimmt die Einträge eines anderen dict dazu.
Ein Sonderfall: Besteht der Text aus einem einzigen Byte, immer wieder, ist der Baum nur ein Blatt, und der Weg dorthin ist leer. Gib diesem Byte dann den Code 0.
Schreib build_tree und code_table. Weil es bei Gleichstand mehrere
richtige Bäume gibt, prüft die Aufgabe nicht die Codes selbst, sondern
ob sie präfixfrei sind und ob der Text mit ihnen so wenige Bits braucht
wie möglich.
Eine Liste von Paaren: trees = counts.map { |byte, count| [count, byte] }.
Solange mehr als eines darin ist: sortieren, die beiden ersten mit
shift herausnehmen, ein neues Paar aus der Summe der Gewichte und
[left, right] hinten anhängen.
Schreib build_tree und code_table. Weil es bei Gleichstand mehrere
richtige Bäume gibt, prüft die Aufgabe nicht die Codes selbst, sondern
ob sie präfixfrei sind und ob der Text mit ihnen so wenige Bits braucht
wie möglich.
Eine Liste von Paaren: trees = [(count, byte) for byte, count in counts.items()].
Solange mehr als eines darin ist: sortieren, die beiden ersten mit
pop(0) herausnehmen, ein neues Paar aus der Summe der Gewichte und
(left, right) hinten anhängen.
encode ist kurz: Für jedes Byte der Code aus der Tabelle, alle hintereinander. join hängt eine Liste von Strings aneinander.
encode ist kurz: Für jedes Byte der Code aus der Tabelle, alle hintereinander. ''.join(…) hängt Strings aneinander.
decode liest die Bits einzeln und geht dabei im Baum nach unten, bei 0 nach links (node[0]), bei 1 nach rechts (node[1]). Kommt es an ein Blatt, hat es ein Byte gefunden, merkt es sich und beginnt wieder oben. Wie bei LZW macht pack('C*') am Schluss aus den Zahlen einen String. Denk auch hier an den Baum, der nur ein Blatt ist.
decode liest die Bits einzeln und geht dabei im Baum nach unten, bei 0 nach links (node[0]), bei 1 nach rechts (node[1]). Kommt es an ein Blatt, hat es ein Byte gefunden, merkt es sich und beginnt wieder oben. Wie bei LZW macht bytes(…) am Schluss aus den Zahlen Bytes. Denk auch hier an den Baum, der nur ein Blatt ist.
Schreib encode und decode.
Beim Decodieren merkst du dir, wo du im Baum gerade bist: node = tree am Anfang, dann für jedes Bit node = node[0] oder node = node[1]. Ist node eine Zahl, ist ein Byte fertig.
Schreib encode und decode.
Beim Decodieren merkst du dir, wo du im Baum gerade bist: node = tree am Anfang, dann für jedes Bit node = node[0] oder node = node[1]. Ist node eine Zahl, ist ein Byte fertig.
Lade die Spezifikation von GIF in den Ordner, einen 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
File.binread('spec-gif89a.txt') liest sie Byte für Byte. Lass dein Programm ausgeben, wie viele Bits encode daraus macht, und probier gleich, ob decode die Datei wieder genau herausgibt.
open('spec-gif89a.txt', 'rb').read() liest sie als Bytes; das b in 'rb' ist wichtig. Lass dein Programm ausgeben, wie viele Bits encode daraus macht, und probier gleich, ob decode die Datei wieder genau herausgibt.
Wie viele Bytes braucht spec-gif89a.txt mit Huffman, also die Zahl
der Bits durch 8, aufgerundet?
encode(data, table).length ist die Zahl der Bits.
Wie viele Bytes braucht spec-gif89a.txt mit Huffman, also die Zahl
der Bits durch 8, aufgerundet?
len(encode(data, table)) ist die Zahl der Bits.
spec-gif89a.txt |
Bytes |
|---|---|
| Original | 83.003 |
| Huffman | dein Ergebnis |
| LZW, mit wachsender Breite der Codes | 28.813 |
gzip -9 |
20.271 |
Huffman sieht jedes Byte einzeln an: Es weiß, dass »e« häufig ist, aber nicht, dass nach »sc« fast immer ein »h« kommt. LZW findet genau solche Folgen und schlägt Huffman bei diesem Text deutlich. Am besten ist, beides zu verbinden: gzip sucht erst Wiederholungen, mit LZ77, einem Verwandten von LZW, und codiert das Ergebnis dann mit Huffman. JPEG macht es ähnlich: Erst werden die Zahlen eines Bildes so umgerechnet, dass viele Nullen entstehen, dann codiert Huffman, was übrig bleibt.
Codiere auch 44-baseline.jpg mit deinem Huffman. Er spart dort nur
gut 50 von 143.206 Bytes. Warum?
Wer decodieren will, braucht die Codes. Anders als bei LZW lassen sie sich nicht beim Lesen erraten; die Tabelle muss mit in die Datei. Den ganzen Baum zu speichern, wäre aufwendig. Es geht mit viel weniger: Es genügt, für jedes Byte die Länge seines Codes zu speichern, wenn beide Seiten die Codes nach derselben festen Regel daraus berechnen. Solche Codes heißen kanonisch:
Aus den Längen A: 2, B: 2, C: 2, D: 3, E: 3 werden so die Codes A = 00, B = 01, C = 10, D = 110 (10 + 1 = 11, mit einer Null dahinter), E = 111. Die Längen sind dieselben wie vorher, also braucht der Text genauso viele Bits, nur die Codes selbst haben sich geändert.
Genau so speichert JPEG seine Huffman-Tabellen im DHT-Segment: 16 Zahlen, wie viele Codes es mit 1, 2, … 16 Bits gibt, dann die Symbole in ihrer Reihenfolge. Daraus berechnet der Decoder die Codes. Ein Code darf bei JPEG höchstens 16 Bits haben; ein Encoder, dessen Baum tiefer wird, muss ihn so umbauen, dass kein Code länger ist.
Schreib canonical(lengths): Es bekommt einen Hash mit der Länge jedes Bytes und gibt einen Hash mit den kanonischen Codes zurück, als Strings aus 0 und 1. code.to_s(2) schreibt eine Zahl als Binärzahl, rjust(length, '0') füllt vorn mit Nullen auf, und << schiebt eine Zahl nach links, hängt also hinten Nullen an: 0b10 << 1 ist 0b100.
Schreib canonical(lengths): Es bekommt ein dict mit der Länge jedes Bytes und gibt ein dict mit den kanonischen Codes zurück, als Strings aus 0 und 1. format(code, 'b') schreibt eine Zahl als Binärzahl, rjust(length, '0') füllt vorn mit Nullen auf, und << schiebt eine Zahl nach links, hängt also hinten Nullen an: 0b10 << 1 ist 0b100. sorted(lengths.items(), key=lambda item: (item[1], item[0])) sortiert nach Länge und dann nach Byte.
Schreib canonical(lengths). Es bekommt einen Hash mit der Länge des
Codes für jedes Byte und gibt die kanonischen Codes zurück.
Merk dir den Code als Zahl und die Länge des vorigen. Für jedes Byte, sortiert mit sort_by { |byte, length| [length, byte] }: erst code <<= length - previous, dann den Code mit code.to_s(2).rjust(length, '0') eintragen, dann code += 1.
Schreib canonical(lengths). Es bekommt einen Hash mit der Länge des
Codes für jedes Byte und gibt die kanonischen Codes zurück.
Merk dir den Code als Zahl und die Länge des vorigen. Für jedes Byte, sortiert nach (length, byte): erst code <<= length - previous, dann den Code mit format(code, 'b').rjust(length, '0') eintragen, dann code += 1.
encode so um, dass es echte Bytes liefert, mit [bits].pack('B*'), und decode passend dazu. Wie erkennt decode, wo die Bits zu Ende sind, wenn das letzte Byte nicht voll ist?encode so um, dass es echte Bytes liefert, mit int(bits[i:i + 8].ljust(8, '0'), 2) für je 8 Bits, und decode passend dazu. Wie erkennt decode, wo die Bits zu Ende sind, wenn das letzte Byte nicht voll ist?