js压缩代码后怎么生成source map_[案例] 压缩算法 —— LZ 算法

ad3abfcde3a8e4fde51787e99d5dee30.png

案例介绍

今天我们来介绍一种无损压缩算法 —— 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 压缩算法的简单介绍,如果有什么问题,或者有什么小想法不知道怎么解决,都可以私信我哦~

4f7adbf3fd5d2ecbb0734909931aa97a.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值