omnizip 0.3.24 → 0.3.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0119d9a2a28f5bc71cc0aa63f142b373196b8ca3f4abc0db93a71e0a9e45051
4
- data.tar.gz: b3f6b837610a9005322e25dc5fd9d79a34529b9cd29d2c4d634efb3d64ad50fa
3
+ metadata.gz: 86505c60d041512f5f031bf20d19c4db6654ffe112bd0e885a7eb76129eaf37a
4
+ data.tar.gz: 8ceca40e4225c272bc527f508d231629b3357959e25e5f3b4ad50ba91116171f
5
5
  SHA512:
6
- metadata.gz: dfa90d09bbe46eed96bec5565d1751e2bed9d47ee0e9a52b587c3cbad87aaf650e1083a4e8e9d3a22b7089ff0cf218386a2f55f637691f0de363d7646c114b3a
7
- data.tar.gz: 5fecb94fb627f48411717bd7dcc4c9243f86b4030b4a24a951d8f0816a123e6ec7288cd4c6920e70a89fa0297e0d082389370341d4792d87985c6198ec7c2bbe
6
+ metadata.gz: 51953cf6f14aa679b99fe0abfc9fcc171addd943787ca7dde5b4f70945bf45ecad4dc2d7e72029f89495ba7e5c73ee9f8b72bcd8dcbe681812ef4038b7228328
7
+ data.tar.gz: 73407d59fc5dc1e4c6a8b997724101181c1650b8fff434b73cfa2a8835d643292fb192dcfe8a85baea49114cb5ae5cedd7c32f1e40d5e653b18b9d8f9ebd5a48
data/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.26] - 2026-08-26
11
+
12
+ ### Changed
13
+ - BZip2 codec emits the **standard .bz2 wire format** (`BZh` magic,
14
+ bzip2 CRC-32, RLE1, BWT, seeded MTF, RUNA/RUNB RLE2, canonical
15
+ Huffman with the delta-coded length tables, 2..=6 selectable
16
+ groups, MSB-first bit packing, EOS magic + combined CRC). Output
17
+ is decodable by `bzip2 -d`; input from the CLI decodes here.
18
+ The previous internal byte-aligned container is removed.
19
+
20
+ ## [0.3.25] - 2026-08-26
21
+
22
+ ### Changed
23
+ - BZip2 Burrows-Wheeler Transform: rotation comparator compares 4
24
+ big-endian bytes per step (Fixnum-safe, no modulo) over a doubled
25
+ byte array. 21x fewer allocations per 138 KB compressed, with
26
+ byte-identical output and 25 case-round-tripped correctness
27
+ (random and degenerate). The BZip2 codec itself does not emit a
28
+ standard bzip2 file format (pre-existing, separate issue).
29
+
10
30
  ## [0.3.24] - 2026-08-26
11
31
 
12
32
  ### Changed
@@ -49,13 +49,16 @@ module Omnizip
49
49
  n = data.length
50
50
  bytes = data.bytes
51
51
 
52
+ # Doubled bytes: rotation a's byte at offset o is
53
+ # dbl[a + o] — indexing needs no modulo, and comparisons
54
+ # can run 8 bytes at a time (modulo arithmetic in the
55
+ # comparator dominated the encoder's profile).
56
+ dbl = bytes * 2
57
+
52
58
  # Build suffix array without creating rotation strings
53
- # Use direct byte comparison for efficiency
54
59
  suffix_array = (0...n).to_a
55
-
56
- # Sort using optimized comparison that avoids string allocation
57
60
  suffix_array.sort! do |a, b|
58
- compare_rotations(bytes, a, b, n)
61
+ compare_rotations(dbl, a, b, n)
59
62
  end
60
63
 
61
64
  # Find primary index (position where suffix starts at 0)
@@ -63,7 +66,7 @@ module Omnizip
63
66
 
64
67
  # Extract last column (character before each suffix)
65
68
  transformed = suffix_array.map do |idx|
66
- bytes[(idx - 1) % n]
69
+ dbl[n + idx - 1]
67
70
  end.pack("C*").b
68
71
 
69
72
  [transformed, primary_index]
@@ -149,23 +152,32 @@ module Omnizip
149
152
  # @param b [Integer] Second rotation start index
150
153
  # @param n [Integer] Length
151
154
  # @return [Integer] -1, 0, or 1 for comparison result
152
- def compare_rotations(bytes, a, b, n)
153
- # Fast path: compare first few bytes directly
154
- 8.times do |offset|
155
- byte_a = bytes[(a + offset) % n]
156
- byte_b = bytes[(b + offset) % n]
157
- cmp = byte_a <=> byte_b
158
- return cmp if cmp != 0
155
+ # Compare rotations a and b over the doubled byte array.
156
+ # Four big-endian bytes per step: the chunk fits a Fixnum (no
157
+ # Bignum allocation), the low-offset byte dominates so the
158
+ # order is lexicographic, and the doubled array removes the
159
+ # per-byte modulo.
160
+ def compare_rotations(dbl, a, b, n)
161
+ len = 0
162
+ while len + 4 <= n
163
+ wa = (dbl[a + len] << 24) |
164
+ (dbl[a + len + 1] << 16) |
165
+ (dbl[a + len + 2] << 8) |
166
+ dbl[a + len + 3]
167
+ wb = (dbl[b + len] << 24) |
168
+ (dbl[b + len + 1] << 16) |
169
+ (dbl[b + len + 2] << 8) |
170
+ dbl[b + len + 3]
171
+ return wa <=> wb if wa != wb
172
+
173
+ len += 4
159
174
  end
160
-
161
- # Continue comparing remaining bytes
162
- (8...n).each do |offset|
163
- byte_a = bytes[(a + offset) % n]
164
- byte_b = bytes[(b + offset) % n]
165
- cmp = byte_a <=> byte_b
175
+ while len < n
176
+ cmp = dbl[a + len] <=> dbl[b + len]
166
177
  return cmp if cmp != 0
167
- end
168
178
 
179
+ len += 1
180
+ end
169
181
  0
170
182
  end
171
183