设为首页 收藏本站
查看: 1282|回复: 0

[经验分享] python 实现的huffman 编码压缩,解码解压缩

[复制链接]

尚未签到

发表于 2015-4-28 05:15:56 | 显示全部楼层 |阅读模式
刚刚实现一个初始版本
1.TODO 仅仅能处理英文,下一步考虑unicode
   似乎考虑多了,当前的程序处理中文文本是一样可以的。
2.TODO enocde ,decode,文本读写多重转换 int -> chr  chr -> int -> bin
  下一步直接读写int,能否直接读写bit?
3.TODO 其它方面考虑速度的优化,比如垃圾回收机制是否影响了速度等等,
   和c/c++比python肯定没有速度优势,不过代码写起来比c/c++舒服多了,感觉python非常接近写伪码的感觉了,所想即所得,
   一个问题只有一个解法,真正让你能够专注与算法与框架流程设计而不被语言本身所束缚。
5.TODO 设计成可以对一些文本一起压缩,全部解压缩或者对指定文本解压缩。
5.特别利用pygraphivz对huffman tree 进行了绘制,有利于调试。见前一篇随笔。
6.TODO 考虑其它压缩方法,如范式huffman 的实现。分词后对词编码而不是对字母编码的方法。


7.压缩过程中写文件遇到的一个问题,因为我只有到扫描处理完所有文件的字符a,b,c...才能计算出最后一个字节剩余了多少个bit,它们被补0,而计算好之后我希望把这个信息写到前面,即压缩文档开头序列化之后马上先记录最后一个byte补了多少个0,然后记录最后一个byte,然后从头到尾顺序记录所有其它被encode translate的byte,所以我先保持了原来的需要写的位置,当写到最后的时候,再把写指针指回,写那个位置,但是我在解压缩过程再读那个位置的时候发现最后的写操作并没有写成功。

        self.infile.seek(0)
        #save this pos we will write here later
        pos = self.outfile.tell()
        self.outfile.write(chr(0))  #store left bit
        self.outfile.write(chr(0))  #if left bit !=0 this is the last byte
        
        #.... translate other bytes
               
        #just after the huffman tree sotre how many bits are left for last
        #byte that is not used and filled with 0
        self.outfile.seek(pos)
        self.outfile.write(chr(leftBit))  #still wrong can't not read well
        self.outfile.write(chr(num))
后来发现要再最后加一句self.outfile.flush()将内容写回硬盘,问题似乎是回写前面的位置,仅仅写到了cache中,最后file.close()的时候该cache的新内容也未被写回硬盘,不知道是不是python2.6的一个bug
反正最后加file.flush()就ok了。



当前流程


压缩过程:
  读文本
  计算各个字符的出现频率
   
   建立huffman tree (二叉连表树实现,不需要parent域)
   
   通过huffman tree 为每个字符编码(深度优先遍历huffman tree,即可得到所以字符编码)
   
   将huffman tree 序列化写到输出文本(以便解压缩时恢复huffman tree,这里采用存储huffman tree 的前序序列,根据huffman tree的特性,每个内部节点均2度,可恢复)
   
   再读文本,为每个字符通过dict 取出它的编码并且写到输出文本。
(注意写的时候集齐8个字符为一组,输出,但是最后一个byte可能不够8位,要用0补齐空位。
   为了处理方便,我将在序列化的二叉树后面首先记录最后一个byte需要用0补齐的位数,如果需要补齐的位    数不为0,则接下来输出最后一个byte,然后再从输入文件内部头开始
   编码输出到输出文件。这里的技巧就是把最后一个byte放到了前面,便于处理,否则解码可能最后文件尾部    会有多余字符被译出。)


解压缩过程:
   读压缩好的文本
   先读文件头部,根据huffman tree前序序列,恢复建立huffman tree,二叉链表树
   
   继续读文本,根据huffman tree 进行解码,0向左,1向右,到叶节点,解码一个字符。
   解码输出完成即完成解压缩。(注意我压缩的时候最后一个byte放到前面了,如果需要要
   将其最后输出。)


当前程序用法
python2.6 huffman.py  input.txt
输出
input.txt.compress  压缩文件
input.txt.compress.de  解压缩后的,内容应与input.txt一致。



allen:~/study/data_structure/huffman$ time python2.6 huffman.py C00-1052.txt


real0m0.607s
user0m0.536s
sys0m0.060s
allen:~/study/data_structure/huffman$ diff C00-1052.txt C00-1052.txt.compress.de
allen:~/study/data_structure/huffman$ du -h C00-1052.txt
36KC00-1052.txt
allen:~/study/data_structure/huffman$ du -h C00-1052.txt.compress.de
36KC00-1052.txt.compress.de
allen:~/study/data_structure/huffman$ du -h C00-1052.txt.compress
24KC00-1052.txt.compress


网上有不少关于huffman的实现,和我这里一样都是采用最简单的基本huffman算法。
做了下对比,采用《平凡的世界》1.7M, 似乎python的效率还不错,不过应该用更大
的文件对比下。另外为什么http://www.javaresearch.org/article/97725.htm中的实现
的压缩比率更大呢,应该压缩率一样的啊。

allen:~/study/data_structure/huffman$ time python2.6 huffman.py normal_world.log


real0m32.236s
user0m31.298s
sys0m0.732s

allen:~/study/data_structure/huffman$ du -h normal_world.log
1.7Mnormal_world.log
allen:~/study/data_structure/huffman$ du -h normal_world.log.compress
1.3Mnormal_world.log.compress
allen:~/study/data_structure/huffman$ du -h normal_world.log.compress.de
1.7Mnormal_world.log.compress.de
allen:~/study/data_structure/huffman$ diff normal_world.log normal_world.log.compress.de


原文件《平凡的世界》,大小1.7M,压缩后1.3M,解压缩后与原文件完全相同,压缩和解压缩共耗时32s


对比http://www.javaresearch.org/article/97725.htm,该java版本,作者提到
压缩效果

使用本程序对《平凡的世界》做压缩测试,压缩前为文本文件,大小为1.7M,压缩后为二进制文件,大小接近1M(988,817byte),而zip压缩后体积为920,997byte,比zip差,压缩文件存储格式待改善。另外,因为从Huffman压缩算法的原理可知,该算法对字符重复率高的文本最有效,比如长篇小说或者英文小说。


另外网上有一个c版本的huffman,http://blog.sina.com.cn/s/blog_4ab057eb0100bx34.html
作者提到:
  l  略大文件  
  test3.txt   《平凡的世界》      
  压缩前:1.62M
  压缩后:1.39M
  压缩率:86%
  压缩时间14.23秒
  解压时间 16.85秒
  测试结果:压缩,解压成功!
           压缩解压时间在可接受范围之内
              




  1 '''
  2 Create a huffman tree from
  3 the input is a list like
  4 [('a',3), ('b',2)]
  5 frequnce of 'a' appeard is stored as it's weight
  6 '''
  7 from Queue import PriorityQueue
  8 #if do not use treeWiter so not include pygraphviz than can use py3.0
  9 from treeWriter import TreeWriter
10 from copy import copy
11
12 class NodeBase():
13     def __init__(self):
14         self.weight = 0
15         
16     def elem(self):
17         return self.weight
18     
19 class Node(NodeBase):
20     def __init__(self, weight = 0, left = None, right = None):
21         self.weight = weight
22         self.left = left
23         self.right = right
24     
25     def __str__(self):
26         return str(self.weight)
27     
28 class Leaf(NodeBase):
29     def __init__(self, key = '', weight = 0):
30         self.key = key
31         self.weight = weight
32         
33     def __str__(self):
34         return str(self.key)
35     
36  
37 def convert(c):
38     '''
39     input c = 'a' ord(a) = 97
40     bin(97) = '0b1100001'
41     return ['0', '1', '1', '0', '0', '0', '0', '1']
42     '''   
43     l1 = list(bin(ord(c))) #like 0b11101
44     l2 = ['0'] * (10 - len(l1))
45     l2.extend(l1[2:])
46     return l2     
47        
48 class HuffmanTree():
49     '''
50     base class for HuffmanTreeForCompress and HuffmanTreeForDecompress
51     '''
52     def __init__(self):     
53         self.root = None
54
55 class HuffmanTreeForCompress(HuffmanTree):   
56     '''
57     create a huffman tree for the compressing process
58     here self.list like [('a',3),('b',4) DSC0000.gif ] where 'a' is key, 3 is weight
59     or say frequence of 'a' appear in the text
60     '''         
61     def __init__(self, list):
62         HuffmanTree.__init__(self)
63         self.list = list #like [('a',3),('b',4)]
64         self.dict = {} #like {'a':[0,1,1,0] , .}
65         
66         self.__buildTree()
67         self.__genEncode()
68         
69     def __initPriorityQueue(self, queue):
70         '''
71         init priority queue let lowest weight at top
72         '''
73         for key, weight in self.list:
74             leaf = Leaf(key, weight)
75             queue.put((weight,leaf))
76            
77     def __buildTree(self):
78         '''
79         build the huffman tree from the list of weight using prority queue
80         greedy alogrithm,choose two least frequence node first
81         '''
82         length = len(self.list)
83         queue = PriorityQueue(length)
84         self.__initPriorityQueue(queue)
85         #while queue.qsize() > 1:
86         # do len(self.list) - 1 times same as while queue.qsize() > 1
87         for i in range(length - 1):
88             left = queue.get()[1]
89             right = queue.get()[1]
90             weight = left.weight + right.weight
91             node = Node(weight, left, right)
92             queue.put((weight,node))
93         self.root = queue.get()[1]
94         
95     def __genEncode(self):   
96         '''
97         get huffman encode for each key using depth first travel of tree
98         '''  
99         def genEncodeHelp(root, encode = []):
100             if isinstance(root, Leaf):
101                 #TODO notice need copy content here,why can't list(encode)?
102                 self.dict[root.key] = copy(encode)
103                 #print self.dict[root.key]
104                 return
105             encode.append(0)
106             genEncodeHelp(root.left, encode)
107             encode[len(encode) - 1] = 1
108             genEncodeHelp(root.right, encode)
109             encode.pop()
110         genEncodeHelp(self.root)
111         
112
113 class HuffmanTreeForDecompress(HuffmanTree):
114     '''
115     rebuild of huffman tree for the decompressing process
116     '''   
117     def __init__(self, infile):
118         HuffmanTree.__init__(self)
119         self.__buildTree(infile)
120     
121     def __buildTree(self, infile):
122         def buildTreeHelp(infile):
123             first = infile.read(1)
124             second = infile.read(1)
125             #if not (first == '\xff' and second == '\xfe'):  #is leaf
126             if first == '\x00':  #is leaf, not consider unicode now
127                 return Leaf(second)
128             node = Node()
129             node.left = buildTreeHelp(infile)
130             node.right = buildTreeHelp(infile)
131             return node
132         infile.read(2)
133         self.root = Node()
134         self.root.left = buildTreeHelp(infile)
135         self.root.right = buildTreeHelp(infile)
136
137 class Decompress():
138     def __init__(self, infileName, outfileName = ''):
139         #TODO better name, expection of opening file
140         self.infile = open(infileName, 'rb')
141         if outfileName == '':
142             outfileName = infileName + '.de'
143         self.outfile = open(outfileName, 'wb')
144         self.tree = None
145         
146     def __del__(self):
147         self.infile.close()
148         self.outfile.close()   
149         
150     def decompress(self):
151         self.__rebuildHuffmanTree()
152         self.__decodeFile()        
153         
154     def __rebuildHuffmanTree(self):
155         self.infile.seek(0)
156         self.tree = HuffmanTreeForDecompress(self.infile)
157         #HuffmanTreeWriter(self.tree).write('tree2.png') #for debug
158         
159     def __decodeFile(self):
160         #right now do not consier speed up using table
161         #do not consider the last byte since it's wrong right now
162         
163         #TODO use a table as 0x00 -> 0000 0000  will speed up?
164         self.outfile.seek(0)
165         leftBit = ord(self.infile.read(1))
166         lastByte = self.infile.read(1)   #it is the last byte if leftBit != 0
167         curNode = self.tree.root
168         #import gc
169         #gc.disable()
170         while 1:
171             c = self.infile.read(1) #how about Chinese caracter? 2 bytes?
172             if c == '':
173                 break
174             li = convert(c) #in c++ you can not return refernce to local in func here ok? yes
175             for x in li:
176                 if x == '0':
177                     curNode = curNode.left
178                 else:
179                     curNode = curNode.right
180                 if isinstance(curNode, Leaf): #the cost of isinstance is higer than lkie root.left == None ?
181                     self.outfile.write(curNode.key)   
182                     curNode = self.tree.root  
183         
184         
185         #deal with the last bye if leftBit != 0
186         #TODO notcice code repeate can we improve?
187         if leftBit:
188             li = convert(lastByte)
189             for x in li:
190                 if x == '0':
191                     curNode = curNode.left
192                 else:
193                     curNode = curNode.right
194                 if isinstance(curNode, Leaf): #the cost of isinstance is higer than lkie root.left == None ?
195                     self.outfile.write(curNode.key)   
196                     curNode = self.tree.root
197                     break    #for the last byte if we find one than it's over,the other bits are useless
198                 
199         self.outfile.flush()
200         #gc.enable()
201                 
202               
203     
204 class Compress():
205     def __init__(self, infileName, outfileName = ''):
206         self.infile = open(infileName, 'rb')
207         if outfileName == '':
208             outfileName = infileName + '.compress'
209         self.outfile = open(outfileName, 'wb')
210         self.dict = {}
211         self.tree = None
212      
213     def __del__(self):
214         self.infile.close()
215         self.outfile.close()
216         
217     def compress(self):
218         self.__caculateFrequence()
219         self.__createHuffmanTree()
220         self.__writeCompressedFile()        
221     
222     def __caculateFrequence(self):
223         '''
224         The first time of reading the input file and caculate each
225         character frequence store in self.dict
226         '''
227         self.infile.seek(0)
228         while 1:
229             c = self.infile.read(1) #how about Chinese caracter? 2 bytes?
230             if c == '':
231                 break
232             #print c
233             if c in self.dict:
234                 self.dict[c] += 1
235             else:
236                 self.dict[c] = 0
237     
238     def __createHuffmanTree(self):
239         '''
240         Build a huffman tree from self.dict.items()
241         '''
242         #TODO for py 3.0 need list(self.dict.items()) instead
243         self.tree = HuffmanTreeForCompress(list(self.dict.items()))
244         #HuffmanTreeWriter(self.tree).write('tree1.png') #for debug
245         
246     def __writeCompressedFile(self):
247         '''
248         Create the compressed file
249         First write the huffman tree to the head of outfile
250         than translate the input file with encode and write the result to
251         outfile
252         '''
253         self.outfile.seek(0)
254         self.__serializeTree()
255         self.__encodeFile()
256         
257     def __serializeTree(self):
258         '''
259         In order to write the tree like node node leaf node .
260         in pre order sequence to the compressed file head
261         here will return the sequence list
262         TODO  reuse pre order and using decorator technic!!
263         list like [(0,0), (0,0), (1,'c')],
264         (0,0) the first 0 means internal node
265         (1,'c') the first 1 means leaf and 'c' is the key
266         '''
267         def serializeTreeHelp(root, mfile):
268             if isinstance(root, Leaf):
269                 mfile.write('\x00') #0x0
270                 mfile.write(root.key)
271                 return
272             mfile.write('\xff') #'\xff' is one character representing 0xff
273             mfile.write('\xfe') #0xfe
274             serializeTreeHelp(root.left, mfile)
275             serializeTreeHelp(root.right, mfile)
276         serializeTreeHelp(self.tree.root, self.outfile)
277            
278     
279     def __encodeFile(self):
280         '''
281         The second time of reading input file
282         translate the input file with encode and write the result to outfile
283         TODO can this be improved speed up?
284         just write \xff as \b 1111 1111 ? can this be possible so do not need
285         to caculate 255 than translate to \xff and write?
286         '''
287         self.infile.seek(0)
288         #save this pos we will write here later
289         pos = self.outfile.tell()
290         self.outfile.write(chr(0))  #store left bit
291         self.outfile.write(chr(0))  #if left bit !=0 this is the last byte
292         num = 0
293         i = 0;
294         while 1:
295             c = self.infile.read(1) #how about Chinese caracter? 2 bytes?
296             if c == '':
297                 break
298             li = self.tree.dict[c]
299             for x in li:
300                 num = (num

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-61276-1-1.html 上篇帖子: Python类型转换 下篇帖子: [Python]使用Python中的config配置
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表