案例介绍
今天我们来介绍一种无损压缩算法 —— LZ 编码,以此来体会压缩解压的魅力。
举个例子,如果我们要发送 “AAA” 这个字符串,那么我们就要发送 3 个字节的数据来存储这个信息,但是,如果将数据压缩,以数字+字符的形式,可能只需要使用 “3A” 作为发送的信息,也就是只需要使用 2 个字节即可完成发送,收到者会解压该信息,理解为由 3 个后面的字符组成的信息流,解压得到 "AAA"。
Lempel Zip(LZ)编码是称为基于字典的编码的那一类算法的一个例子,它是用其发明者的名字(Abraham Lempel 和 Jacob Ziv)命名的。在通信会话的时候它将会产生一个字符串字典。如果接受和发送双方都有这样的字典,那么字符串可以由字典中的索引代替,以减少通信的数据的传输量。这种算法有不同的版本(LZ77、LZ78等)。
—— 《计算机科学导论 第三版》
准备工作
异常处理
在学习 Python 的时候,我们编写的代码可能会出错,这些错误会被编译器所捕捉到,显示给我们看。比如:
ZeroDivisionError
以上是系统会自动触发判断出的异常,基本上你只要在控制台看到了这些错误,程序就会停止运行,类似于 Linux 中的 panic() 命令效果,系统宕机,不过我们的程序只是会停止运行,不会导致计算机崩溃。
我们自己怎么主动触发异常呢?
Python 为我们提供了 raise 语句,我们可以在需要的时候,自己编写 raise 语句来终止程序运行。
举一个例子,编写一个数值判断程序:
x = 10
if x > 5:
print("x 大于 5.")
if x == 10:
raise RuntimeError("x 非法.")
if x > 0:
print("x 大于 0.")
该程序首先给 x 赋值为 10,然后使用 3 个 if 语句,来分别进行判断,你猜程序运行结果是什么?
x 大于 5.
-------------------------------------------------------------------------------
LZW_main.py 130 <module>
raise RuntimeError("x 非法.")
RuntimeError:
x 非法.
Process finished with exit code 1
类似上述,程序打印了字符串“x 大于 5”,raise 了出错信息“x 非法”,但是本应该会执行的第 3 个 if 并没有执行,我们并没有看到打印的字符串,所以,当 raise 了一个错误类型后,会发出一个异常,程序会终止执行。
我们该如何自己捕获异常呢?
下面这个实例来自菜鸟教程,目的是为了说明我们如何编写代码捕获处理异常:
try:
runoob()
except AssertionError as error:
print(error)
else:
try:
with open('file.log') as file:
read_data = file.read()
except FileNotFoundError as fnf_error:
print(fnf_error)
finally:
print('这句话,无论异常是否发生都会执行。')
使用 try-except-else-finally 结构,当然这其中的关键字不是必须的,其中 except 用来捕获可能的异常,如果出错就会执行 except 下面的代码,如果没有出错就会执行 else 下面的代码,而 finally 中的代码无论出错与否都会执行。发现了吗?只要我们使用异常捕获语句,我们就能够让程序即使发生了异常也能运行下去。
isinstance() 函数
isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。
>>> x = "123"
>>> isinstance(x, str)
True
生成器
在 Python 中,使用了 yield 的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。—— 菜鸟教程
生成器函数并不会将所有结果都计算返回,它只会保存某次计算的"环境",以进行下一次运算,你使用 next() 方法,每调用一次生成器,那么它就会返回一个结果,你不停地调用,它就会不停地给你返回结果。
举一个我们程序中用到的一个生成器的例子,该生成器会依据给定的初始值(如果没有给定默认为 1),你每调用一次生成器,他就会给你返回一个在之前基础上加 1 的数字用作下标。
def get_index_generator(index_start=None):
"""
索引生成器
从 index_start 开始, 否则从 1 开始
:return: 下标索引值
"""
i = 1 if index_start is None else index_start # 三元表达式,用来标定下标从哪里开始,默认为 1.
while True:
yield i
i += 1
g = get_index_generator(0)
for i in range(10):
print(next(g))
g = get_index_generator(-5)
for i in range(10):
print(next(g))
运行结果如下:
0
1
2
3
4
5
6
7
8
9
-5 # 指定下标从 -5 开始
-4
-3
-2
-1
0
1
2
3
4
一种特殊的数据结构 —— 有序字典
有序字典和通常的字典类型类似,但是与一般字典不同,它可以保存元素插入的顺序,而不是任意顺序迭代。
样例程序:
d = collections.OrderedDict()
d[0] = 0
d[1] = 10
d[2] = 20
d[3] = 30
for k, v in d.items():
print(k, v)
运行结果
0 0
1 10
2 20
3 30
算法思想
压缩:以纯英文字符串为例,逐个遍历每个字符串,如果当前字典表中还没有该字符,则将该字符加入字典中,键为该字符,值为生成器返回的数字,若有当前字符,则继续加入下一个字符,此时便有两个字符组成了字符串,判断该字符串是否在当前字典中,如果在,则继续添加下一个字符,组成新的字符串,如果不在,则将该字符串加入字典。最终构成的输出字符串,是用字典中的值代替字符串+末尾字符的形式。
解压:传入字符串是数字+字符构成的,故取其中的数字和字符来构建字典,再反向通过查找字典,将传入字符串中的数字用字符串来代替。
代码汇总
# coding: utf-8
# !/usr/bin/python
"""
@File : LZW_main.py
@Author : jiaming
@Modify Time: 2020/8/30 20:59
@Contact : CSDN-专业IT技术社区-登录
@微信公众号答疑: codenough
@Desciption : 数据压缩 —— Lempel Zip 编码
本算法为 LZ 编码的简单演示,仅限于纯英文文本
该种编码基于字典。
"""
import collections
import pretty_errors
message = "BAABABBBAABBBBAC" # BA2B3B1A4B5C
def showError(ErrorType=None):
"""
出错处理
:param ErrorType: 错误类型
:return:
"""
if ErrorType is TypeError:
raise TypeError("""
1. maybe index_start is not int(get_index_generator()).
2. maybe charter is not pure English words(encoding_msg()).
3. maybe data is not str or pure English words(dataIsAlpha()).
""")
raise RuntimeError("Unexpected error.")
def dataIsAlpha(data=""):
"""
如果 data 不纯英文返回 False 否则 raise
:param data:
:return:
"""
if isinstance(data, str) is False:
showError(TypeError)
if data.isalpha() is False:
showError(TypeError)
else:
return True
def get_index_generator(index_start=None):
"""
索引生成器
从 index_start 开始, 否则从 1 开始
:return: 下标索引值
"""
if isinstance(index_start, int) is False and index_start is not None:
showError(TypeError)
i = 1 if index_start is None else index_start
while True:
yield i
i += 1
def encoding_msg(msg=""):
"""
:param msg: 预压缩的字符串
:return: 压缩后的字符串
"""
map = collections.OrderedDict() # 映射表 map['A'] = 1
INDEX_GENERATOR = get_index_generator() # 构造 generator 给 INDEX_GENERATOR
subWords = '' # 保存中间子串
result_str = "" # 保存最终压缩结果
print("待压缩字符流:t{0}, 长度:{1}.".format(msg, len(msg)))
for i in msg:
subWords += i
if subWords not in map.keys():
map[subWords] = next(INDEX_GENERATOR) # 映射表 map['A'] = 1
if len(subWords) > 1:
result_str += str(map[subWords[:-1]]) + subWords[-1]
else:
result_str += subWords
subWords = ''
print("dictionary:")
for k, v in map.items():
print("{0}t-->t{1}".format(k, v))
print("压缩结果:t{0}, 长度:{1}".format(result_str, len(result_str)))
return result_str
def decoding_msg(msg=""):
"""
解压缩字符流
:param msg:
:return:
"""
if dataIsAlpha(message):
pass
print("待解压字符流:t{0}, 长度:{1}.".format(msg, len(msg)))
map = collections.OrderedDict() # 映射表 map[1] = 'A'
INDEX_GENERATOR = get_index_generator()
subWords = '' # 保存中间子串
result_str = "" # 保存最终压缩结果
for i in msg:
subWords += i
if subWords not in map.values() and i.isalpha():
if len(subWords) > 1:
subWords = map[int(subWords[:-1])] + subWords[-1]
map[next(INDEX_GENERATOR)] = subWords
result_str += subWords
else:
map[next(INDEX_GENERATOR)] = subWords
result_str += subWords
subWords = ''
print("解压缩结果:t{0}, 长度{1}".format(result_str, len(result_str)))
if __name__ == "__main__":
if dataIsAlpha(message):
pass
decoding_msg(msg=encoding_msg(msg=message))
运行结果
待压缩字符流: BAABABBBAABBBBAC, 长度:16.
dictionary:
B --> 1
A --> 2
AB --> 3
ABB --> 4
BA --> 5
ABBB --> 6
BAC --> 7
压缩结果: BA2B3B1A4B5C, 长度:12
待解压字符流: BA2B3B1A4B5C, 长度:12.
解压缩结果: BAABABBBAABBBBAC, 长度16
Process finished with exit code 0
算法特点
- 对于数据流中连续重复出现的字节和字串,该压缩技术有很高的压缩比。
- 其不仅可以用于文本程序,还能够用于图像数据处理。
- 对于机器硬件要求不高。
后记
以上就就是 LZ 压缩算法的简单介绍,如果有什么问题,或者有什么小想法不知道怎么解决,都可以私信我哦~