python 实现的huffman 编码压缩,解码解压缩

刚刚实现一个初始版本
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

real 0m0.607s
user 0m0.536s
sys 0m0.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
36K C00-1052.txt
allen:~/study/data_structure/huffman$ du -h C00-1052.txt.compress.de 
36K C00-1052.txt.compress.de
allen:~/study/data_structure/huffman$ du -h C00-1052.txt.compress
24K C00-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

real 0m32.236s
user 0m31.298s
sys 0m0.732s
allen:~/study/data_structure/huffman$ du -h normal_world.log
1.7M normal_world.log
allen:~/study/data_structure/huffman$ du -h normal_world.log.compress
1.3M normal_world.log.compress
allen:~/study/data_structure/huffman$ du -h normal_world.log.compress.de 
1.7M normal_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)] 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 +  x 
301                  i  +=   1
302                   if  (i  ==   8 ):
303                      self.outfile.write(chr(num))
304                      num  =  0
305                      i  =  0
306           # for all left bit we will fill with 0,and fil finally save left bit
307           # like the last is 11 wich has 6 bits left than will store the last
308           # byte as 1100,0000
309          leftBit  =  ( 8   -  i) % 8   
310           if  leftBit: 
311               for  j  in  range(i,  8 ):
312                  num  =  (num  <<   1 )
313                  
314           # just after the huffman tree sotre how many bits are left for last 
315           # byte that is not used and filled with 0
316          self.outfile.seek(pos)
317          self.outfile.write(chr(leftBit))   # still wrong can't not read well
318          self.outfile.write(chr(num))
319          self.outfile.flush()   # well need this, why? remember !!!!
320           # self.outfile.seek(0,2)   #will not write success without this a bug???
321           # print self.outfile.read(1)
322          
323          
324          
325    #    def test(self):
326    #        for k, v in self.dict.items():
327    #            print k
328    #            print v
329              
330 
331  class  HuffmanTreeWriter(TreeWriter):
332       '''
333      draw a huffman tree to tree.png or user spcified file
334      For huffman debug only
335       '''
336       def  writeHelp(self, root, A):
337          p  =  str(self.num)
338          self.num  +=   1
339          
340           if  isinstance(root, Leaf):
341              key  =  root.key   # TODO '\n' wrong to fix
342               # key.replace('\n', '\\n')
343               # A.add_node(p, label = str(root.elem()) + r'\n' + key, shape = 'rect')
344              A.add_node(p, label  =  str(root.elem())  +  r ' \n ' , shape  =   ' rect ' )
345               return  p
346          
347           # if not a leaf for huffman tree it must both have left and right child
348          A.add_node(p, label  =  str(root.elem()))
349 
350          q  =  self.writeHelp(root.left, A)
351          A.add_node(q, label  =  str(root.left.elem()))
352          A.add_edge(p, q, label  =   ' 0 ' )
353         
354          r  =  self.writeHelp(root.right, A)
355          A.add_node(r, label  =  str(root.right.elem()))
356          A.add_edge(p, r, label  =   ' 1 ' )
357              
358          l  =  str(self.num2)
359          self.num2  -=   1
360          A.add_node(l, style  =   ' invis ' )
361          A.add_edge(p, l, style  =   ' invis ' )
362          B  =  A.add_subgraph([q, l, r], rank  =   ' same ' )
363          B.add_edge(q, l, style  =   ' invis ' )
364          B.add_edge(l, r, style  =   ' invis ' )
365          
366           return  p   # return key root node        
367 
368 
369 
370 
371  if   __name__   ==   ' __main__ ' :
372       # d = [chr(ord('a')+i) for i in range(13)]
373       # w = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]
374       # list = []
375       # for i in range(13):
376       #     list.append((d[i], w[i]))
377       # print(list)
378       # tree = HuffmanTreeForCompress(list)
379       # writer = HuffmanTreeWriter(tree)
380       # writer.write()
381       # tree.test()
382       import  sys
383       if  len(sys.argv)  ==   1 :
384          inputFileName  =   ' test.log '
385       else :
386          inputFileName  =  sys.argv[ 1 ]
387      compress  =  Compress(inputFileName)
388      compress.compress()
389      
390      decompress  =  Decompress(inputFileName  +   ' .compress ' )
391      decompress.decompress()
392          
393       # compress.test()
394 

    
   
  
   


你可能感兴趣的:(python 实现的huffman 编码压缩,解码解压缩)