home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_100 / 181_01 / lzwcom.doc < prev    next >
Text File  |  1985-03-10  |  18KB  |  408 lines

  1.  
  2.                 LZWCOM - FILE COMPRESSOR UTILITY
  3.                         by KENT WILLIAMS
  4.  
  5.                           INTRODUCTION
  6.  
  7.      Most information processed by computers contains redundancy 
  8. - repeated characters, words, phrases, etc.  Since computers do 
  9. not have infinite storage, or infinite time in which to transfer 
  10. information, there is much interest in ways to remove redundancy 
  11. from data. The LZWCOM program implements the Lempel/Ziv/Welch 
  12. data compression algorithm, and allows files to be made smaller, 
  13. for transmission and storage. 
  14.  
  15.      Users of RCPMs (Remote CP/M computers) and other bulletin 
  16. board facilities may be familiar with the SQUEEZE and UNSQUEEZE 
  17. programs.  These programs are the standard way to compress and 
  18. uncompress information for transfer by modem.  
  19.  
  20.      These programs use the Huffman Encoding algorithm, which 
  21. exploits the fact that certain characters are more likely to 
  22. appear in a stream of data than others.  The input data is 
  23. translated such that frequently occuring characters are 
  24. represented by short bit strings, and infrequently occuring 
  25. characters by longer bit strings.
  26.  
  27.      Huffman encoding performs very badly upon data that does not 
  28. conform to the assumptions made about character frequency.  The 
  29. SQUEEZE program gets around this fact by taking two passes 
  30. through a file during the compression process: one to gather 
  31. statistical information on the data, and a second to actually 
  32. encode it.  A table giving the coding scheme used is added to the 
  33. front of the file to facilitate decoding.
  34.  
  35.      There are some problems with this method.  It involves the 
  36. overhead of making two passes through a file, a time penalty, and 
  37. attaching a table to the resulting compressed file, a space 
  38. penalty.  It also will perform poorly on data in which there is 
  39. no clear pattern of character frequency, such as object code and 
  40. floating point numbers.  Object files will sometimes actually 
  41. grow when passed through Huffman encoding.
  42.  
  43.                     THE LEMPEL/ZEV ALGORITHM
  44.  
  45.      Lempel/Zev encoding exploits redundancy on a different 
  46. level.  Instead of viewing data as a stream of characters, it 
  47. views it as a sequence of strings.  A table of strings is built 
  48. during encoding, whose members have this property: If a string 
  49. <<string><one_character>> is in the table, then <string> is in 
  50. the table as well.
  51.  
  52.      Each element of the string table contains two elements: a 
  53. predecessor, which is the index of the prefix string, and a 
  54. follower, which is the suffix character.  The string signified by 
  55. a particular element is the string composed of the predecessor 
  56. string plus the suffix string.
  57.  
  58.      Therefore, if one starts out with a table initialized to 
  59. one-character strings, one can represent new strings by the index 
  60. of a string already in the table, and one character.  If a string 
  61. already in the table is encountered, then the index of the string 
  62. in the table can be written to output, rather than the string 
  63. itself.  
  64.  
  65.      This means that it can yield better results than Huffman 
  66. encoding on data that contains repeated character strings, but a 
  67. nearly even distribution of character frequency, such as object 
  68. code produced by compilers.  Its best-case compression potential 
  69. is also markedly superior, as one code can theoretically 
  70. represent a string nearly as long as the table is large, whereas 
  71. Huffman compression's best case is representing one character by 
  72. one bit, or 1:8.
  73.  
  74.      Worst case performance is more difficult to determine, but I 
  75. have never seen LZ compression expand data, whereas I have 
  76. observed Huffman encoding doing just that.
  77.  
  78.      The convention generally followed is to use a table allowing 
  79. 4096 strings, so that a twelve-bit code can be used to represent 
  80. each string.  This seems to provide the best trade-off between 
  81. table size and code length - to use a shorter code would unduly 
  82. limit the number of possible strings, and a longer code would 
  83. make the table prohibitively large.  Also 12 bit codes are 
  84. relatively easy to pack and unpack with a microcomputer.
  85.  
  86. COMPRESSION ALGORITHM
  87.  
  88.      The compressor makes one pass through the source file, 
  89. progressively biting off larger strings, dynamically building the 
  90. string table.  The decompressor is able to repeat the process in 
  91. reverse, proceeding from the fact that it starts from the same 
  92. table of one character, or 'atomic' strings.  
  93.  
  94.      The mechanics of the algorithm are actually more difficult 
  95. to describe and comprehend than they are to actually implement.  
  96. This psuedo-code should help to explain it.
  97.  
  98.      assume a table whose elements contain:
  99.         a predecessor code, which is either NO_PREDECESSOR
  100.         or the index of another element in the table 
  101.         (signifying a prefix string), 
  102.         and a 'follower character' (the last character of
  103.         the string.
  104.  
  105.      the function 'code' combines these two elements to 
  106.      produce a unique code (i.e. an index into the table)
  107.      for the resulting string.
  108.  
  109.      initialize the table to 'atomic' strings, i.e. 
  110.      (NO_PREDECESSOR,character).
  111.      read a character current_input_char from input
  112.      Current_Code = code(NO_PREDECESSOR, C)
  113.  
  114.      WHILE getchar(current_input_char) <> end of file
  115.        IF code(Current_Code, C) is in the table THEN
  116.           Current_Code = code(Current_Code,C)
  117.        ELSE
  118.           write Current_Code to output
  119.           IF there is still room in the string table THEN
  120.             add code(Current_Code,C) to the string table
  121.           ENDIF
  122.           Current_Code = code(NO_PREDECESSOR,C)
  123.        ENDIF
  124.      ENDWHILE
  125.      write Current_Code to output ( always one left )
  126.  
  127.  
  128.      The first character to be translated is a special case, in 
  129. that the code for it is written immediately to output.  (As I 
  130. have implemented the algorithm, the codes correspond directly to 
  131. the index of an entry in the string table.)  It then serves as a 
  132. 'seed' for the rest of the processing.
  133.  
  134.      Whether it is the first, or any following pass through the 
  135. main loop, the last known code (Current_Code) is used as the 
  136. starting point for parsing a string from input.  It is combined 
  137. with the character read from input to generate a new code.  If 
  138. that new code is in the table already, then it becomes 
  139. Current_Code, and you go back to the top of the loop to get 
  140. another character.  
  141.  
  142.      When code(Current_Code,current_input_char) isn't in the 
  143. string table, Current_code is written to output.  The string 
  144. code(Current_Code,current_input_char) is added to the string 
  145. table.  Code(NO_PRED,current_input_char) becomes Current_Code.
  146.  
  147.      When end of file is reached, you fall out of the loop, write 
  148. the last code to output, and exit.
  149.  
  150.      The actual code to implement this process (outside of sundry 
  151. housekeeping and support functions) takes up 30 lines.  Though 
  152. the concepts behind it may seem elusive, the algorithm has a 
  153. certain elegance.
  154.  
  155. DECOMPRESSION ALGORITHM
  156.  
  157.      Reversing the process is a little more complex, as I found 
  158. out during debugging.  For several days it seemed that I had 
  159. acheived irreversible data compression.
  160.  
  161. The psuedocode:
  162.  
  163.      initialize table (same as compression)
  164.      Current_Code = Old_Code = Getcode
  165.  
  166.      output String_table[Current_code].follower (first character 
  167.                                                  of file)
  168.      Final_Char = String_table[Current_Code].follower
  169.  
  170.      WHILE getcode(Current_Code) <> end of file
  171.  
  172.         Intermediate = Current_Code   (save a copy for later)
  173.  
  174.         IF Current_Code isn't in the table (a special case)
  175.            output Final_Char
  176.            Current_Code = Old_Code
  177.            Intermediate = code(Old_Code,Final_Char) (save for later)
  178.         ENDIF
  179.  
  180.         WHILE String_table[Current_code].predecessor <> NO_PREDECESSOR 
  181.         (while we haven't reached 'atomic' code)
  182.            push(String_table[Current_code].follower
  183.            (decode the string backwards into a stack)
  184.            Current_Code = String_table[Current_Code].predecessor
  185.            (walk backwards up the string)
  186.         endwhile
  187.  
  188.         Output String_table[Current_Code].follower
  189.         (first character in string)
  190.         & assign it to Final_Char
  191.  
  192.         WHILE stack isn't empty
  193.           pop characters and write them to output
  194.        
  195.         Add code(Old_Code,Final_Char) to string table
  196.         Old_Code = Intermediate (This is the later
  197.                                  we were saving it for)
  198.      ENDWHILE          
  199.  
  200.  
  201.      Decompression hinges on the fact that with one exception, 
  202. the algorithm is never presented with a code that isn't either 
  203. atomic, or one that it has already added to the table itself.  
  204. Decoding, therefore is a matter of decoding strings in reverse of 
  205. the way they were decoded.
  206.  
  207.      The one ugly exception happens when the compressor 
  208. encounters a string of the form
  209.  
  210.      <C><string><C><string><C>
  211.      
  212. where <C> is any character, <string> is any string, and 
  213. <<C><string>> is already in the table.
  214.       
  215.      The problem is that the compressor writes the code for 
  216. <<C><string>> to output, and then adds < <<C><string>> <C> > to 
  217. the string table.
  218.  
  219.      The decompressor will also have <<C><string>> in the table, 
  220. but it will not have < <<C><string>> <C> >, which is what the 
  221. compressor will send it next.
  222.  
  223.      Luckily (and someone smarter than I has verified this) this 
  224. is the only time the compressor will encounter an unknown code, 
  225. and for this case, the following relationships pertain:
  226.  
  227.      1. The uncompressor knows that the unknown code is an
  228.      extension of the prior code.
  229.      2. The most recently translated character is the first
  230.      character of the prior code.
  231.      3. The final character of the unknown code is the same as 
  232.      the first character of the prior code.   
  233.  
  234.      Once any unknown codes are taken care of, it becomes simple 
  235. to repeatedly pull off the last character of the code, push it on 
  236. a stack  and set the code variable to its predecessor, until the 
  237. predecessor is NO_PREDECESSOR.  Once this is the case, the 
  238. follower character of that code is sent to output.  Then the 
  239. characters on the stack, if any, are popped off and sent to 
  240. output.  The table is updated with the string produced by the 
  241. prior code and the current final character, and you go back to 
  242. the top of the loop.
  243.  
  244. THE PROGRAM
  245.  
  246.      The program will compile and run properly, so long as the 
  247. standard C environment is supported by the compiler, up to and 
  248. including the UNIX C compiler.  If you plan to use BDS C, or 
  249. another non-standard compiler, areas of incompatability are 
  250. isolated in the file I/O section at the end of COMMLZW.C.  Other 
  251. possible problem areas might be:
  252.  
  253.      1.  Command line arguments, which are often not supported by
  254.          subset compilers.
  255.  
  256.      2.  The use of long integers in the function h() in 
  257.          COMMLZW.C.  The function will work with 16-bit
  258.          integers, but you lose several bits of precision,
  259.          robbing the mid-square hash function of some of its
  260.          randomness.
  261.  
  262.      3.  The '#ifdef' preprocessor directive is not supported
  263.          by all subset compilers.  These can be removed with no
  264.          ill effect.
  265.  
  266.      The '#ifdef DEBUG' statements have been left in the source 
  267. code, to allow you to view the compression of text files on the 
  268. console.  If the program is compiled with DEBUG defined, and you 
  269. compress or decompress a text file, the codes being read or 
  270. written, along with the characters that they stand for, will be 
  271. sent to standard output.  This will give you a better grasp of 
  272. the operation of the algorithm.
  273.  
  274.      The source code modules LZWCOM.C and LZWUNC.C are the 
  275. compressor and uncompressor main programs respectively.  Aside 
  276. from housekeeping code at the top and bottom, they are very 
  277. literal translations of the psuedocode presented above.
  278.  
  279.      LZWUNC uses a pointer to the current entry extensively to 
  280. try and minimize the overhead of calculating offsets into arrays.  
  281. This is (hopefully) dealt with in a fairly straightforward 
  282. manner.
  283.  
  284.      COMMLZW.C contains the routines to support maintenance of 
  285. the string table, and the peculiar I/O requirements of the 
  286. program.
  287.  
  288.      Since the program spends 90% of its time putting things into 
  289. and looking things up in the string table, I have tried to make 
  290. this process more efficient.  Each element of the string table 
  291. contains a member called 'next,' which points either to the next 
  292. element that hashed to that location in the table, or to -1.  If 
  293. a collision occurs on hash, a linked list is built, consisting of 
  294. all elements that hashed to that location.
  295.  
  296.      This became necessary because as the table became full, it 
  297. took longer and longer to determine whether a particular code was 
  298. in the table, or to find an empty slot to use for a new entry.  
  299. On my Z80 system, the program would 'go away' for minutes at a 
  300. time, processing one sector.  
  301.  
  302.      Now, if a particular string is not in a relatively short 
  303. linked list, it isn't in the table.  When a slot is needed for a 
  304. new string, searches through 'full' areas are minimized.
  305.  
  306. COMPILING
  307.  
  308.      The files LZWCOM.C, LZWUNC.C, and COMMLZW.C are all compiled 
  309. seperately.  LZWCOM and LZWUNC must then each be linked with 
  310. COMMLZW and the standard library.  The submit file I used with 
  311. Aztec C is
  312.  
  313.      cz -f -q lzwcom
  314.      as -zap lzwcom
  315.      cz -f -q lzwunc
  316.      as -zap lzwunc
  317.      cz -f -q commlzw
  318.      as -zap commlzw
  319.      ln lzwcom.o commlzw.o c.lib
  320.      ln lzwunc.o commlzw.o c.lib
  321.  
  322.      '-f' forces the compiler to do in-line frame allocation, and 
  323. '-q' tells it to promote automatic variables to statics.  These 
  324. switches speed up execution somewhat, in theory. The '-zap' 
  325. option on assembly kills the assembly source file when the 
  326. assembler gets done.
  327.  
  328.      Different compilers, I am sure, require different switches, 
  329. but the basic principle of seperate compilation will hold here.
  330.  
  331. RUNNING
  332.  
  333.      LZWCOM and LZWUNC both take two command line arguments: a 
  334. source file name and a destination file name.  The compressor 
  335. reads an uncompressed file as its source and outputs a compressed 
  336. file as its destination.  The decompressor, of course, operates 
  337. in reverse.
  338.  
  339. EXAMPLE (on a CP/M system)
  340.  
  341.      A>LZWCOM COMPRESS.DOC COMPRESS.DQC
  342.      .............................................................
  343.      A>LZWUNC COMPRESS.DQC COMPRESS.DOC
  344.      ................................
  345.  
  346.      Both programs print a period for every sector read.  It 
  347. would probably be desirable to suppress this if you regularly 
  348. uncompress text files with the console as the output file, as the 
  349. periods will interfere somewhat with viewing the file on the 
  350. screen.  This should cause no problems with sending the output of 
  351. the uncompressor to the printer, or other devices as desired.
  352.  
  353.      On a 4 MHZ Z-80 CP/M system, the compressor can process a 
  354. sector about every 2 seconds, slowing down slightly when the 
  355. string table begins to fill up.  The uncompressor runs slightly 
  356. faster than that.  If you are using a more advanced system, your 
  357. results should be better.
  358.  
  359.  
  360. POSSIBLE ENHANCEMENTS 
  361.  
  362.      This method of compression will tend to lose some efficiency 
  363. on very large files, as when the string table gets filled up, the 
  364. compressor no longer adapts to the data.  It might be a good idea 
  365. in those cases to break longer files into blocks of 16K or so, 
  366. and reinitialize the table between blocks.
  367.  
  368.      The readc and writec routines could be changed to buffer 
  369. more data at each disk access.  As it stands, if you get input 
  370. from one floppy disk and output to another one, the drive motors 
  371. keep up a regular tango beat.
  372.  
  373.      The source code, as it stands, is as optimized as I could 
  374. make it.  The program can, however, be speeded up markedly by 
  375. converting the the hash and unhash routines to assembly code.  
  376. The program spends most of its time putting things into and 
  377. taking things out of the string table, so this can improve run 
  378. time quite a bit. Just doing some simple hand 'peephole' 
  379. optimization of the assembly source produced by the Aztec 
  380. compiler produced noticable improvement.
  381. .pa
  382.                           RESULTS/CONCLUSIONS
  383.  
  384.      In my experience, these programs produce better compression 
  385. ratios in about the same time as the SQ17.COM program in the 
  386. public domain.  Terry Welch (who I assume is the Welch in 
  387. Lempel/Ziv/Welch) in his article on the subject (IEEE Computer, 
  388. June 1984) did more extensive testing, and came up with these 
  389. results.
  390.  
  391.      DATA TYPE                     COMPRESSION RATION
  392.      ________________________________________________
  393.      English Text                       1.8
  394.      Cobol Files                      2 to 6
  395.      Floating Point Arrays              1.0
  396.      Formatted Scientific Data          2.1
  397.      System Log Data                    2.6
  398.      Program Source Code                2.3
  399.      Object Code                        1.5
  400.  
  401.      These programs should provide a good way to transfer and 
  402. store data in a more compressed form.  They are written to be 
  403. portable to a wide variety of systems - virtually any system with 
  404. a standard C compiler.  They are also an implementation of an 
  405. elegant algorithm - of interest in its own right.
  406.  
  407. em with 
  408. a standard C compiler.  They are also an implementation