bwrl_file_compression.sf 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #!/usr/bin/ruby
  2. # Author: Trizen
  3. # Date: 21 June 2023
  4. # https://github.com/trizen
  5. # Compress/decompress files using Burrows-Wheeler Transform (BWT) + Variable length-run encoding + Huffman coding.
  6. # Reference:
  7. # Data Compression (Summer 2023) - Lecture 13 - BZip2
  8. # https://youtube.com/watch?v=cvoZbBZ3M2A
  9. define(
  10. CHUNK_SIZE = (1 << 17),
  11. SIGNATURE = ('BWRL' + 1.chr),
  12. LOOKAHEAD_LEN = 128,
  13. )
  14. func read_bit (FileHandle fh, Ref bitstring) {
  15. if ((*bitstring \\ '').is_empty) {
  16. *bitstring = unpack('b*', fh.getc \\ die "error")
  17. }
  18. var bit = (*bitstring).substr(-1)
  19. *bitstring = (*bitstring).substr(0, -1)
  20. return bit
  21. }
  22. func read_bits (FileHandle fh, Number bits_len) {
  23. var str = ''
  24. fh.read(\str, bits_len>>3)
  25. str = unpack('B*', str)
  26. while (str.len < bits_len) {
  27. str += unpack('B*', fh.getc \\ die "error")
  28. }
  29. if (str.len > bits_len) {
  30. str.substr!(0, bits_len)
  31. }
  32. return str
  33. }
  34. func bwt_sort (String s) { # O(n * LOOKAHEAD_LEN) space (fast)
  35. ^s.len -> map {|i|
  36. var t = s.slice(i, LOOKAHEAD_LEN)
  37. if (t.len < LOOKAHEAD_LEN) {
  38. t += s.slice(0, min(i, LOOKAHEAD_LEN - t.len))
  39. }
  40. [t, i]
  41. }.sort {|a,b|
  42. (a[0] <=> b[0]) || (s.rotate(a[1]) <=> s.rotate(b[1]))
  43. }.map { .[1] }
  44. }
  45. func bwt_encode(String s) {
  46. var bwt = bwt_sort(s)
  47. var ret = bwt.map {|i| s.slice(i-1, 1) }.join
  48. var idx = bwt.first_index_by { .is_zero }
  49. return (ret, idx)
  50. }
  51. func bwt_decode(String bwt, Number idx) { # fast inversion
  52. var tail = bwt.chars
  53. var head = tail.sort
  54. var indices = Hash()
  55. tail.each_kv {|i,v|
  56. indices{v} := [] << i
  57. }
  58. var table = []
  59. head.each_kv {|i,v|
  60. table[i] = indices{v}.shift
  61. }
  62. var dec = ''
  63. var i = idx
  64. head.len.times {
  65. dec += head[i]
  66. i = table[i]
  67. }
  68. return dec
  69. }
  70. func delta_encode (Array integers) {
  71. var deltas = [0, integers.len, integers...].diffs
  72. var bitstring = FileHandle.new_buf(:raw)
  73. for d in (deltas) {
  74. if (d == 0) {
  75. bitstring << '0'
  76. }
  77. else {
  78. var t = d.abs.as_bin
  79. bitstring << join('', '1', ((d < 0) ? '0' : '1'), ('1' * (t.len-1)), '0', t.substr(1))
  80. }
  81. }
  82. pack('B*', bitstring.parent)
  83. }
  84. func delta_decode (FileHandle fh) {
  85. var deltas = []
  86. var buffer = ''
  87. var len = 0
  88. for (var k = 0 ; k <= len ; ++k) {
  89. var bit = read_bit(fh, \buffer)
  90. if (bit == '0') {
  91. deltas << 0
  92. }
  93. else {
  94. var bit = read_bit(fh, \buffer)
  95. var n = (^Inf -> first { read_bit(fh, \buffer) != '1' })
  96. var d = Num('1' + n.of { read_bit(fh, \buffer) }.join, 2)
  97. deltas << (bit == '1' ? d : -d)
  98. }
  99. if (k == 0) {
  100. len = deltas.pop
  101. }
  102. }
  103. var acc = [len, deltas...].acc
  104. acc.shift
  105. return acc
  106. }
  107. func walk(Hash n, String s, Hash h) {
  108. if (n.has(:a)) {
  109. h{n{:a}} = s
  110. return nil
  111. }
  112. walk(n{:0}, s+'0', h) if n.has(:0)
  113. walk(n{:1}, s+'1', h) if n.has(:1)
  114. }
  115. func mktree_from_freq(Hash freqs) {
  116. var nodes = freqs.sort_by {|k| k.to_i }.map_2d { |b,f|
  117. Hash(a => b, freq => f)
  118. }
  119. loop {
  120. nodes.sort_by!{|h| h{:freq} }
  121. var(x, y) = (nodes.shift, nodes.shift)
  122. if (defined(x)) {
  123. if (defined(y)) {
  124. nodes << Hash(:0 => x, :1 => y, :freq => x{:freq}+y{:freq})
  125. }
  126. else {
  127. nodes << Hash(:0 => x, :freq => x{:freq})
  128. }
  129. }
  130. nodes.len > 1 || break
  131. }
  132. var n = nodes.first
  133. walk(n, '', n{:tree} = Hash())
  134. return n
  135. }
  136. func huffman_encode(Array bytes, Hash t) {
  137. join('', t{bytes...})
  138. }
  139. func huffman_decode (String bits, Hash tree) {
  140. bits.gsub(Regex('(' + tree.keys.sort_by { .len }.join('|') + ')'), {|s| tree{s} })
  141. }
  142. func create_huffman_entry (Array bytes, FileHandle out_fh) {
  143. var freq = bytes.freq
  144. var h = mktree_from_freq(freq){:tree}
  145. var enc = huffman_encode(bytes, h)
  146. var max_symbol = (bytes.max \\ 0)
  147. say "Max symbol: #{max_symbol}";
  148. var freqs = []
  149. for i in (0 .. max_symbol) {
  150. freqs << (freq{i} \\ 0)
  151. }
  152. out_fh << delta_encode(freqs)
  153. out_fh << pack("N", enc.len)
  154. out_fh << pack("B*", enc)
  155. }
  156. func decode_huffman_entry (FileHandle fh) {
  157. var freqs = delta_decode(fh)
  158. var freq = Hash()
  159. freqs.each_kv {|k,v|
  160. if (v != 0) {
  161. freq{k} = v
  162. }
  163. }
  164. var rev_dict = mktree_from_freq(freq){:tree}.flip
  165. for k in (rev_dict.keys) {
  166. rev_dict{k} = "#{rev_dict{k}} "
  167. }
  168. var enc_len = unpack('N', 4.of { fh.getc }.join).to_i
  169. say "Encoded length: #{enc_len}"
  170. if (enc_len > 0) {
  171. return huffman_decode(read_bits(fh, enc_len), rev_dict).nums
  172. }
  173. return []
  174. }
  175. func VLR_encoding (Array bytes) {
  176. var uncompressed = []
  177. var bitstream = FileHandle.new_buf(:raw)
  178. for c,v in (bytes.run_length) {
  179. uncompressed << c
  180. if (v == 1) {
  181. bitstream << '0'
  182. }
  183. else {
  184. var t = v.as_bin
  185. bitstream << ('1'*(t.len-1) + '0' + t.substr(1))
  186. }
  187. }
  188. return (uncompressed, bitstream.parent)
  189. }
  190. func VLR_decoding (Array uncompressed, FileHandle fh) {
  191. var decoded = FileHandle.new_buf(:raw)
  192. var buffer = ''
  193. uncompressed.len.times {
  194. var c = uncompressed.shift.chr
  195. var bl = (^Inf -> first { read_bit(fh, \buffer) != '1' })
  196. if (bl > 0) {
  197. decoded << c*Num('1' + bl.of { read_bit(fh, \buffer) }.join, 2)
  198. }
  199. else {
  200. decoded << c
  201. }
  202. }
  203. return decoded.parent
  204. }
  205. func compression (String chunk, FileHandle out_fh) {
  206. var (bwt, idx) = bwt_encode(chunk)
  207. say "\nBWT index = #{idx}"
  208. var (uncompressed, lengths) = VLR_encoding(bwt.bytes)
  209. out_fh << pack('N', idx)
  210. create_huffman_entry(uncompressed, out_fh)
  211. create_huffman_entry(pack('B*', lengths).bytes, out_fh)
  212. }
  213. func decompression (FileHandle fh, FileHandle out_fh) {
  214. var idx = unpack('N', 4.of { fh.getc \\ die "error" }.join).to_i
  215. say "\nBWT index = #{idx}"
  216. var uncompressed = decode_huffman_entry(fh)
  217. var lengths = decode_huffman_entry(fh).pack('C*')
  218. var bwt = VLR_decoding(uncompressed, lengths.open_r(:raw))
  219. out_fh << bwt_decode(bwt, idx)
  220. }
  221. # Compress file
  222. func compress_file(File input, File output) {
  223. var in_fh = input.open('<:raw') || die "Can't open file <<#{input}>> for reading"
  224. var out_fh = output.open('>:raw') || die "Can't open file <<#{output}>> for writing"
  225. var header = SIGNATURE
  226. # Print the header
  227. out_fh.print(header)
  228. # Compress data
  229. while (in_fh.read(\var chunk, CHUNK_SIZE)) {
  230. compression(chunk, out_fh)
  231. }
  232. # Close the files
  233. in_fh.close
  234. out_fh.close
  235. }
  236. # Decompress file
  237. func decompress_file(File input, File output) {
  238. var in_fh = input.open('<:raw') || die "Can't open file <<#{input}>> for reading"
  239. if (SIGNATURE.len.of { in_fh.getc }.join != SIGNATURE) {
  240. die "Not a BWRL archive!\n"
  241. }
  242. var out_fh = output.open('>:raw') || die "Can't open file <<#{output}>> for writing"
  243. while (!in_fh.eof) {
  244. decompression(in_fh, out_fh)
  245. }
  246. in_fh.close
  247. out_fh.close
  248. }
  249. ARGV.getopt!('d', \var decode)
  250. var file = File(ARGV.shift) || do {
  251. say "usage: #{File(__MAIN__).basename} [-d] [input file]"
  252. Sys.exit(2)
  253. }
  254. if (decode || file.match(/\.bwrl\.enc\z/)) {
  255. decompress_file(file, File("output.bwrl.dec"))
  256. }
  257. else {
  258. compress_file(file, File("output.bwrl.enc"))
  259. }