LZW编码采用隐字典码的形式完成压缩。
文本中符号并不是独立存在的,前一个符号对后一个符号的出现有着很大影响。
LZW算法利用文本开头部分的特征,作为整个文本的特征,即利用文本开头部分的内容,生成固定规模的字典,即符号–码符号对,然后利用于整个文本,将文本符号转化为码符号。
LZW首先要有初始字典(可以为空),再通过文本符号添加字典中的元素。每次读取文本中一个符号,若该符号已出现在字典中,则将改符号作为前缀,继续读取下一位。
例如
已有字典{‘A’:0,‘B’:1,‘C’:2},字符串’AAABABC’,
- 第一步读取’A’,‘A’已存在于字典中,所以’A’作为前缀,读取字符串后一位’A’,此时当前符号为’AA’,没在字典中,于是字典添加符号–码符号对’AA’:3,字典更新为{‘A’:0,‘B’:1,‘C’:2,‘AA’:3}。
- 清空前缀,读取字符串第三位’A’,‘A’在字典中,于是’A’作为前缀,读取下一位’B’,当前符号为’AB’,没在字典中,于是添加符号–码符号对’AB’:4,字典更新为{‘A’:0,‘B’:1,‘C’:2,‘AA’:3,‘AB’:4}
- 清空前缀,读取字符串第五位’A’,‘A’位于字典中,于是’A’作为前缀,读取下一位’B’,当前符号为’AB’,‘AB’在字典中,于是’AB’作为前缀,读取下一位’C’,当前符号为’ABC’,没在字典中,于是字典更新为{‘A’:0,‘B’:1,‘C’:2,‘AA’:3,‘AB’:4,‘ABC’:5}。
{‘A’:0,‘B’:1,‘C’:2,‘AA’:3,‘AB’:4,‘ABC’:5}即为{‘A’:0,‘B’:1,‘C’:2}根据字符串’AAABABC’生成的字典。
LZW常用ASCII码作为初始字典,生成字典的值从255开始。实现时,更新字典的同时需要记录下文本符号对应的码符号。
具体步骤为
- 生成初始字典
keys=[]
values=[]
for i in range(255):
keys.append(chr(i))
values.append(i)
- 更新字典
Path='C:\Users\88466\Desktop\Gone with the wind.txt'# 文本路径
with open(Path,'r') as f1:
data_string=fi.read()
digit=12 #生成字典的大小,12即表示字典有2的12次方个元素,常取12,16
Compress_list=[] #存放文本符号对应的码符号
pW_s='' #前缀字符串
temp_index=len(keys)
max_time=len(data_string)
i=0
while(i<max_time):
cW_s=data_string[i] #当前字符串
#该结构用于判断是否data_string是否全部读取完毕
try:
sW_s=data_string[i+1]
except:
Compress_list.append(values[keys.index(cW_s)])
break
temp_index=temp_index+1
if cW_s+sW_s in keys:
while(cW_s+sW_s in keys):
i=i+1
cW_s=cW_s+sW_s
try:
sW_s=data_string[i+1]
except:
Compress_list.append(values[keys.index(cW_s)])
break
keys.append(cW_s+sW_s)
values.append(temp_index-1)
try:
Compress_list.append(values[keys.index(cW_s)])
except:
print('未记录:',cW_s)
i=i+1
if len(keys)>=2**digit:
break
#keys与values即为更新的字典
- 利用字典将文本符号转为码符号
while(i<max_time):
b=0
cW_s=data_string[i]
try:
sW_s=data_string[i+1]
except:
Compress_list.append(values[keys.index(cW_s)])
break
while(cW_s+sW_s in keys):
i=i+1
cW_s=cW_s+sW_s
try:
sW_s=data_string[i+1]
except:
Compress_list.append(values[keys.index(cW_s)])
break
try:
Compress_list.append(values[keys.index(cW_s)])
except:
print('未记录:',cW_s)
i=i+1
#Compress_list为0~2^digit的数字,即为文本符号对应的码符号。
- 压缩
#将列表转为二进制字符串
def list2bit_string(li):
bitstring=''
for i in range(len(li)):
temp_string=bin(li[i]).split('b')[1]
while(len(temp_string)<digit):
temp_string='0'+temp_string
bitstring=bitstring+temp_string
return bitstring
Compress_bit_string=list2bit_string(Compress_list)
print('字符串生成完成,字符串总长度为',len(Compress_bit_string))
#将二进制字符串转为字节流
def bit_string2bytes(bs):
bt=bytearray()
time=round(len(bs)/8)
for i in range(time):
bs_k=int(bs[i*8:(i+1)*8],2)
bt.append(bs_k)
bt.append(int(bs[-4:],2))
return bytes(bt)
Compress_bytes=bit_string2bytes(Compress_bit_string)
print('字节流生成完成')
with open('Compress_Gone with the wind','wb') as f2:
f2.write(Compress_bytes)
print('存储完成')
- 解压缩,由于采用的是隐字典,即字典隐藏在码符号中,解压缩时要将字典还原回去。
(1)获取码符号
with open('Compress_Gone with the wind','rb') as f3:
Compress_bytes=f3.read()
#将二进制字节流转为二进制字符串流
def bytes2bitstring(byte):
bs=''
for i in byte:
bs_k=str(bin(i)).split('b')[1]
while len(bs_k)<8:
bs_k='0'+bs_k
bs=bs+bs_k
bs=bs
return bs
Compress_bit_string=bytes2bitstring(Compress_bytes)
print('字符串转换完成,字符串总长度为',len(Compress_bit_string))
#将字符串流转为列表
def bitstring2list(string):
li=[]
time=round(len(string)/digit)-1
for i in range(time):
temp_s=string[digit*i:digit*(i+1)]
li.append(int(temp_s,2))
return li
Compress_list=bitstring2list(Compress_bit_string)
print('列表转换完成,列表总长度为',len(Compress_list))
(2)生成解压缩字典
例如已有字典{‘A’:0,‘B’:1,‘C’:2},码符号[0,1,1,3,6,2]
- 读取码符号0,0在字典中有对应’A’,即表明生成字典时,第一个位置的符号在字典中,但其作为前缀,连接下一位符号就已经不在字典内了,下一个码符号为1,对应’B’,所以此次添加元素为’AB’:3,字典更新为{‘A’:0,‘B’:1,‘C’:2,‘AB’:3}。
- 读取码符号1,1在字典中有对应’B’,下一个码符号为1,对应’B’,字典更新为{‘A’:0,‘B’:1,‘C’:2,‘AB’:3,‘BB’:4}
- 读取码符号1,1在字典中有对应’B’,下一个码符号为3,对应’AB’,表明在生成该字典元素,读取到了码符号1对应的’B’及码符号3对应的首符号’A’时添加了元素,即此次添加元素为’BA’:5,字典更新为{‘A’:0,‘B’:1,‘C’:2,‘AB’:3,‘BB’:4,‘BA’:5}
- 读取码符号3,3在字典中有对应’AB’,字典此次添加的符号定以’AB’开头,下一个码符号为6,其首符号为’A’,所以此次添加元素’ABA’:6,字典更新为{‘A’:0,‘B’:1,‘C’:2,‘AB’:3,‘BB’:4,‘BA’:5,‘ABA’:6}
- 读取码符号2,2在字典中对应’C’,且已为最后一个码符号,字典不再更新。
由上述步骤可见,每读取一个码符号,就要再字典中添加一次元素