Идея этого алгоритма основана на том предположении, что входящие в сжимаемый
текст слова неоднократно повторяются. Сжатие достигается заменой обнаруженных
повторов ссылками на предшествующие вхождения этих же слов. Т.е. повторное
вхождение слова заменяется парой чисел <Расстояние до предыдущего вхождения
слова, Длина слова>. Под словом понимается не слово естественного языка, а
произвольная последовательность символов.
Последнее предложение (Под словом понимается не слово естественного
языка, а произвольная последовательность символов) может
быть записано так: Под словом понимается не<21,6> ест<3,3>венного языка, а
произвольная<56,3>следовате<17,3>ость сим<3,3>ов.
Всего восемнадцать символов удалось заменить кодами - не очень много,
но при сжатии большого текста результат может быть гораздо лучше. Предполагается
также, что поиск совпадений производится не во всем тексте, а лишь в его части,
непосредственно предшествующей обрабатываемому слову. В приведенных примерах
длина этой части шестнадцать килобайт.
При записи в архив несжатый символ предваряется нулем - 0<символ>,
ссылка на ранее встречавшееся слово - единицей, т.е. 1SSSSSSSSSSSSSSNNNNN
(SSSSSSSSSSSSSS - расстояние до предыдущего входения, NNNNN - длина слова).
Это не самый экономный способ, но для начала ов вполне подойдет. Нужно только решить,
как кодировать конец файла. Можно пожертвовать самым большим смещением и использовать
его в качестве кода конца файла, а можно использовать код 1111111111111110, в этом
случае для максимального смещения придется использовать код 1111111111111111
(все коды двоичные) - так и будет сделано.
Программная состоит из четырех функций - Encode (выполняет кодирование), Decode
(выполняет декодирование), Save и Load (побитовые запись и чтение архива). Вот
как может выглядеть функция Encode:
byte Txt [TXT_LEN]; // Текст
word nTxt;
word pTxt;
byte Arc [ARC_LEN]; // Архив
word nArc;
word pArc;
void Save(word C; word N); // запись N бит
void Encode(word hArc, hTxt)
nTxt=0;
pTxt=0;
pArc=0;
while TRUE do // цикл просмотра текста
if pArc>=BLK_LEN then // запись в архив
write(hArc,@Arc,BLK_LEN);
puts (".");
word I=BLK_LEN;
word J=0;
while I=FRM_LEN+BLK_LEN then // сдвиг окна
word I=BLK_LEN;
word J=0;
while I=nTxt then
nTxt=nTxt+read(hTxt,@Txt[nTxt],BLK_LEN);
end
if pTxt>=nTxt then
exit
end
int I=pTxt-(FRM_LEN+(OFS_MIN-1));
if I<0 then
I=0;
end
int S=0;
int L=0;
while I+OFS_MIN<=pTxt do // поиск повторяющихся строк
int J=0;
while TRUE do // J=pTxt then
exit
end
if J>=STR_LEN then
exit
end
if pTxt+J>=nTxt then
exit
end
inc J;
end
if L<=J then
S=I;
L=J;
end
inc I;
end
if L>=PAK_MIN then // запись ссылки
S=pTxt-S;
Save(1,1);
Save(S-OFS_MIN,OFS_LEN);
if S=FRM_LEN+OFS_MIN-1 then
Save(1,1);
end
Save(L-PAK_MIN,PAK_LEN);
pTxt=pTxt+L;
else
Save(0,1);
Save(Txt[pTxt],8);
inc pTxt;
end
end
Save(1,1);
Save(FRM_LEN,OFS_LEN);
Save(0,1);
if pTmp<15 then // нужно для корректного завершения
Save(0,16);
end
if pArc>0 then
write(hArc,@Arc,pArc);
end
end
Эта функция работоспособна, но содержит погрешности и лишние действия. В ней
реализован наивный подход к решению задачи, в худшем случае внутренний цикл
выполняется около FRM_LEN*STR_LEN раз. И так получилось, что похожая функция
была использована для тестирования компиляторов Context'а на различных машинах,
но здесь она принесла некоторую пользу.
Вот что можно улучшить. Во-первых, кодируемое и кодирующее слова могут пересекаться,
недопустимо лишь их полное наложение. Поэтому выход из цикла по условию I+J>=pTxt
не требуется и условие внешнего цикла нужно заменить на I<pTxt. Условия J>=STR_LEN
и pTxt+J>=nTxt можно заменить одним J>=N, где N - меньшее из STR_LEN и nTxt-pTxt.
Эти и некоторые другие улучшения приведены ниже:
void Encode(word hArc, hTxt)
nTxt=0;
pTxt=0;
pArc=0;
while TRUE do
if pArc>=BLK_LEN then
write(hArc,@Arc,BLK_LEN);
puts(".");
word I=BLK_LEN;
word J=0;
while I=FRM_LEN+BLK_LEN then
word I=BLK_LEN;
word J=0;
while I=nTxt then
nTxt=nTxt+read(hTxt,@Txt[nTxt],BLK_LEN);
end
if pTxt>=nTxt then
exit
end
int I=pTxt-FRM_LEN;
if I<0 then
I=0;
end
int N=STR_LEN;
if N>nTxt-pTxt then
N=nTxt-pTxt;
end
byte T=Txt[pTxt];
int S=0;
int L=0;
int J=0;
while TRUE do
while TRUE do
if Txt[I+J]!=Txt[pTxt+J] then
exit
end
if J>=N then
exit
end
inc J;
end
if L<=J then
if I=pTxt then
exit
end
S=I;
L=J;
end
inc I;
while Txt[I]!=T do
inc I;
end
J=1;
end
if L>=PAK_MIN then
S=pTxt-S;
Save(1,1);
Save(S-1,OFS_LEN);
if S=FRM_LEN then
Save(1,1);
end
Save(L-PAK_MIN,PAK_LEN);
pTxt=pTxt+L;
else
Save(0,1);
Save(Txt[pTxt],8);
inc pTxt;
end
end
Save(1,1);
Save(FRM_LEN-1,OFS_LEN);
Save(0,1);
if pTmp<15 then
Save(0,16);
end
if pArc>0 then
write(hArc,@Arc,pArc);
end
end
Этот алгоритм обеспечивает приемлемое сжатие (примерно в 1.1-1.5 раза хуже,
чем распространенные DOS-архиваторы), а вот его быстродействие очень низко.
Для демонстрации идеи это не важно, для практического использования алгоритм
должен быть усовершенствован. Для поиска совпадений можно использовать алгоритм
Кнута-Морриса-Пратта, но он даст выигрыш лишь в худшем случае (например, когда
сжимаемый текст состоит из одних пробелов). Индексный поиск дает выигрыш лишь
при большой ширине окна. Можно использовать хэш-таблицу, но в этом случае высокая
скорость достигается за счет явного ограничения длин цепочек коллизий и найденные
совпадения могут не быть наилучшими. Можно использовать комбиникованные алгоритмы.
Наконец степень сжатия можно повысить за счет более оптимального кодирования, например
за счет преобразования смещений, длин и несжатых символов в коды Хаффмана. Это также
позволит увеличить ширину окна и максимальную длину совпадения.
В заключение - полный текст программы. Содержательная часть начинается с первого
define:
define TXT_LEN 32768
define FRM_LEN 16384
define ARC_LEN 4096
define BLK_LEN 2048
define ARC_FWD 32
define END_LEN 16
define STR_LEN 34
define PAK_LEN 5
define PAK_MIN 3
define OFS_LEN 14
struct Addr
word Ofs;
word Seg;
section
void @Ptr;
end
void @Ptr(word Seg,Ofs)
Addr P;
P.Seg=Seg;
P.Ofs=Ofs;
return @P.Ptr;
end
word GetPSP()
asm mov AH,62H
asm int 21H
asm mov AX,BX
end
word create(char @Name)
asm push DS
asm mov AH,3CH
asm mov CX,00H
asm mov DX,SS:[BP+6]
asm mov DS,DX
asm mov DX,SS:[BP+4]
asm int 21H
asm pop DS
end
word open(char @Name)
asm push DS
asm mov AH,3DH
asm mov AL,00H
asm mov DX,SS:[BP+6]
asm mov DS,DX
asm mov DX,SS:[BP+4]
asm int 21H
asm pop DS
end
word read(word F; void @Buff; word N)
asm push DS
asm mov AH,3FH
asm mov BX,SS:[BP+10]
asm mov CX,SS:[BP+4]
asm mov DX,SS:[BP+8]
asm mov DS,DX
asm mov DX,SS:[BP+6]
asm int 21H
asm pop DS
end
word write(word F; void @Buff; word N)
asm push DS
asm mov AH,40H
asm mov BX,SS:[BP+10]
asm mov CX,SS:[BP+4]
asm mov DX,SS:[BP+8]
asm mov DS,DX
asm mov DX,SS:[BP+6]
asm int 21H
asm pop DS
end
void close(word F)
asm mov AH,3EH
asm mov BX,SS:[BP+4]
asm int 21H
end
void putc(char Ch)
asm mov AH,2
asm mov DL,SS:[BP+4]
asm int 21H
end
void puts(char @St)
word P=0;
while St[P]!=#0 do
putc(St[P]);
inc P;
end
end
int strcmp(char @S1, @S2)
int P=0;
while TRUE do
if S1[P]S2[P] then
return 1;
end
if S1[P]=#0 then
return 0;
end
inc P;
end
end
byte Txt [TXT_LEN];
word nTxt;
word pTxt;
byte Arc [ARC_LEN];
word nArc;
word pArc;
word P2 [16];
word Tmp;
word pTmp;
void Save(word C; word N)
if N>8 then
Save(C/256,N-8);
C=C%256;
N=8;
end
if pTmp<8 then
Arc[pArc]=Tmp/256;
inc pArc;
Tmp =256*(Tmp%256);
pTmp=pTmp+8;
end
Tmp =Tmp+P2[pTmp-(N-1)]*C;
pTmp=pTmp-N;
end
word Load(word N)
word C=0;
if N>8 then
C=256*Load(N-8);
N=8;
end
if pTmp>7 then
if pArc=BLK_LEN then
write(hArc,@Arc,BLK_LEN);
puts(".");
word I=BLK_LEN;
word J=0;
while I=FRM_LEN+BLK_LEN then
word I=BLK_LEN;
word J=0;
while I=nTxt then
nTxt=nTxt+read(hTxt,@Txt[nTxt],BLK_LEN);
end
if pTxt>=nTxt then
exit
end
int I=pTxt-FRM_LEN;
if I<0 then
I=0;
end
int N=STR_LEN;
if N>nTxt-pTxt then
N=nTxt-pTxt;
end
byte T=Txt[pTxt];
int S=0;
int L=0;
int J=0;
while TRUE do
while TRUE do
if Txt[I+J]!=Txt[pTxt+J] then
exit
end
if J>=N then
exit
end
inc J;
end
if L<=J then
if I=pTxt then
exit
end
S=I;
L=J;
end
inc I;
while Txt[I]!=T do
inc I;
end
J=1;
end
if L>=PAK_MIN then
S=pTxt-S;
Save(1,1);
Save(S-1,OFS_LEN);
if S=FRM_LEN then
Save(1,1);
end
Save(L-PAK_MIN,PAK_LEN);
pTxt=pTxt+L;
else
Save(0,1);
Save(Txt[pTxt],8);
inc pTxt;
end
end
Save(1,1);
Save(FRM_LEN-1,OFS_LEN);
Save(0,1);
if pTmp<15 then
Save(0,16);
end
if pArc>0 then
write(hArc,@Arc,pArc);
end
end
void Decode(word hArc, hTxt)
nArc=0;
pArc=0;
pTxt=0;
while TRUE do
if pTxt>=FRM_LEN+BLK_LEN then
write(hTxt,@Txt,BLK_LEN);
word I=BLK_LEN;
word J=0;
while I=nArc then
word I=pArc;
word J=0;
while I0 then
write(hTxt,@Txt,pTxt);
end
exit
end
end
int N=Load(PAK_LEN)+PAK_MIN;
int P=pTxt-S;
while N>0 do
Txt[pTxt]=Txt[P];
inc pTxt;
inc P;
dec N;
end
end
end
end
begin
char @Line=@Ptr(GetPSP(),129);
byte @Size=@Ptr(GetPSP(),128);
char Parm [3][128];
int F=1;
int I=0;
int J=0;
while I<3 do
while J