Lempel–Ziv–Welch

Lempel–Ziv–Welchは、1984年に辞書式圧縮である Lempel-Ziv法 (LZ78) を、スペリー社のテリー・ウェルチが改良したアルゴリズムで、開発者のLempel、Ziv、Welchの頭文字を取って命名された。略称はLZW。
圧縮効率と高速化の両面を追求している為、LZSSとハフマン符号化を組み合わせたDeflateアルゴリズム（LZHやZIP、PNGなどが採用）と比べると30%ほど圧縮効率が悪い。GIFで利用されている他、TIFFやPDFの圧縮でLZWを選択可能。UNIX Compressで使える。

アルゴリズム

LZ78と違い、最初に入力可能なすべての文字を辞書に追加して初期化しておくため、部分文字列は辞書に必ず存在し、出力はコードだけの配列となる。「コード」とは辞書に登録されている文字列に対応するインデックスのことである。Welchの1984年の論文では8ビットが並んだデータを12ビット固定長のコード列としてエンコードしていた。0から255のコードは対応する1つの8ビット文字を表し、256から4095のコードは辞書にない文字列がデータに出現し、辞書にその文字列を追加するときに順次割り振られる。
このアルゴリズムは同じパターンが繰り返されるデータに最適に働く。辞書に追加しながらエンコードするため文字列の最初の部分は低圧縮率となるが、文字列が増えるに連れ圧縮率はしだいに最大へと近づく。
「文字列」と表現しているが入力は文字列でなくともよいため、他のデータの圧縮にもすぐに用いられた。例えば、カラーテーブルを使った画像では1文字はカラーテーブルのインデックスに対応する。しかし、1980年代には多くの画像が16色程度の小さなカラ―テーブルしか持っていなかったため、画像が大きくない限り12ビット幅のコードでは小さな圧縮率しか得られなかった。このため可変幅コードのアイディアが導入された。コードはエンコードしているシンボルより一般的には1ビット広い幅から始め、各コードサイズが使い切られるにつれて、コード幅は1ビットずつ広げられ、予め決められた最大値(一般的には12ビット)まで広げられる。最大コード値まで達した時は、エンコーディングは既存の辞書を使用して続けられるが、新たなコードが作られたり、辞書に追加されることはない。
その他の改善には、辞書をクリアーして初期状態に復元することを示すコード(クリアーコード、一般的には個別のアルファベット文字の値のすぐ後の最初の値)や、データの終わり(ストップコード、一般的にはクリアーコードより1大きい)を示すコードを辞書に確保することが含まれる。クリアーコードはテーブルが満杯になった後に再初期化し、エンコーディングが入力データのパターンの変化に対応することを可能にする。賢いエンコーダーは圧縮効率を監視し、既存のテーブルが入力に合っていないときはいつでも辞書をクリアーすることができる。
デコーダーは出力されたコード列だけでエンコーダに使われたのと同じ辞書をデコードしながら再び作ることができるため、完全な辞書をエンコードされたデータと一緒に送る必要はない。このためエンコーダーとデコーダーがどの種類のLZWが使われているかー―1文字のサイズ、最大辞書サイズ(とコード幅)、可変幅のエンコーディングが使われているかどうか、初期コード幅、クリアーコード・ストップコードが使われているかどうか(そしてコード値はなにか)ーーについて合意していることが重要である。LZWを採用している多くのフォーマットではこの情報はフォーマット仕様に盛り込まれているか、圧縮データのヘッダーにこれらの情報のための明確なフィールドが確保されている。

可変幅コード

もし可変幅コードが使われている場合、エンコーダーとデコーダーはエンコードされたデータの同じ位置でコード幅の変更が行われなくてはならない。一般的なバージョンではエンコーダーは文字列W + sが辞書になかったが、次に辞書で利用可能なコードが2ᵖであったときに幅をpからp + 1へ増やす(コードを辞書に追加しなければならないため)。エンコーダーはWのコードを幅pで出力に送出する。そして次のコードからp + 1ビット幅で送出できるようにコード幅を増やす。
デコーダーはいつも辞書の作成でエンコーダーより1コード分遅れており、Wのコードを見るとき、それは2ᵖ − 1のコードを生成する。エンコーダーがコード幅を増やすポイントであるから、デコーダーもpビットで最大のコードを生成するポイントであるここで同じように幅を増やさなければならない。
不幸なことに、初期に実装されたいくつかのエンコーディングアルゴリズムはコード幅を増やした後、古い幅ではなく新しい幅でWを送出する。デコーダーには1コード分早く変化したと見えるため、これは"Early Change"と呼ばれる。この違いは大きな混乱を招くため、アドビはPDFファイルではどちらのバージョンも許容しているが、それぞれのLZW圧縮ストリームのヘッダーにEarly Changeが使われているかどうかを示す明示的なフラグを含めている。LZW圧縮が使用可能な画像ファイルフォーマットのうち、TIFFはEarly Changeを使うが、GIFとその他多くの画像ファイルフォーマットでは使っていない。
クリアーコードによって辞書がクリアーされた時、エンコーダーとデコーダーの両方はコード幅をクリアーコードのあと初期のコード幅に戻し、クリアーコードの後すぐにそのコードから開始する。

パッキング順序

コードの送出は一般的にはバイト境界に一致しないため、エンコーダーとデコーダーはどのようにコードをバイトに詰め込むかを合意しておかなければならない。一般的な2つの方法はLSB-First("Least Significant Bit First")とMSB-First("Most Significant Bit First")である。
GIFはパッキング順序にLSB-Firstを使い、TIFFとPDFはMSB-Firstを使う。

実装

以下、Groovyでの実装。まず、ビット列を扱うストリームを用意する。
class BitStream {
  BitSet bs = new BitSet(); int len = 0, pos = 0;
  void write(int v, int bits) {
    for (int i in 0..<bits) { bs[len++] = ((v >>> i) & 1) != 0 }
  }
  int read(int bits) {
    int v = 0; for (int i in 0..<bits) { if (bs[pos++]) { v |= 1 << i } }
    return v
  }
  String toString() { "length = $len, {" + (0..<len).findAll({ bs[it] }).join(", ") + "}" }}
圧縮は以下の通り。
BitStream compress(byte[] data) {
  BitStream bs = new BitStream(); List str = []; int maxCode = 255, maxCodeBits = 8;
  Map table = [:]; for (int i in 0..maxCode) { table[[(byte) i]] = i }

  for (byte c in data) {
    str << c
    if (!table.containsKey(str)) {
      bs.write(table[str[0..(str.size() - 2)]], maxCodeBits)
      table[str] = ++maxCode
      if (maxCode == (1 << maxCodeBits)) maxCodeBits++
      str = [c]
    }
  }
  bs.write(table[str], maxCodeBits)
  return bs}
解凍は以下の通り。
byte[] decompress(BitStream bs) {
  List bytes = []; int maxCode = 255, maxCodeBits = 8, prevCode; byte c;
  List table = []; for (byte v in 0..maxCode) { table << [v] }

  bs.pos = 0
  bytes << (c = prevCode = bs.read(maxCodeBits))
  while (bs.pos < bs.len) {
    if (++maxCode == (1 << maxCodeBits)) maxCodeBits++
    int code = bs.read(maxCodeBits)
    List str = (code == maxCode) ? table[prevCode] + c : table[code]
    bytes.addAll(str)
    table << table[prevCode] + (c = str[0])
    prevCode = code
  }
  return bytes as byte[]}

例

今回圧縮する平文（アルファベットの大文字のみで表される）は
TOKYOTOKKYOKYOKAKYOKU#
である。#は文字列の終端を表す。
この時、使用される文字は27種類（アルファベットおよび#）である。
この例では、1～26の数字をアルファベットに、
0を#に当てはめる(ストップコードが0)。27種類を表すために必要な最小のビット幅は5なので、5ビットから始める。