lut.zig (5860B)
1 //! This module provides functions for compressing the range of a dataset 2 //! using bitmaps and lookup tables. 3 4 const std = @import("std"); 5 const assert = std.debug.assert; 6 const expect = std.testing.expect; 7 8 const u16_range = std.math.maxInt(u16) + 1; 9 const bitmap_size = u16_range >> 3; 10 11 const BitmapResult = struct { 12 bitmap: []u8, 13 min_non_zero: u16, 14 max_non_zero: u16, 15 }; 16 17 fn generateBitmap( 18 data: []const u16, 19 allocator: std.mem.Allocator, 20 ) !BitmapResult { 21 const bitmap = try allocator.alloc(u8, bitmap_size); 22 @memset(bitmap, 0); 23 // array index = upper 13 bits 24 // bit index = lower 3 bits 25 for (data) |x| { 26 bitmap[x >> 3] |= (@as(u8, 1) << @as(u3, @intCast(x & 7))); 27 } 28 // don't store zero, its existence is assumed 29 bitmap[0] &= 0b11111110; 30 // compute the first and last non zero bytes 31 var min_non_zero: u16 = bitmap_size - 1; 32 var max_non_zero: u16 = 0; 33 for (0..bitmap_size) |i| { 34 const j: u16 = @intCast(i); 35 if (bitmap[j] == 0) continue; 36 min_non_zero = @min(j, min_non_zero); 37 max_non_zero = @max(j, max_non_zero); 38 } 39 assert(min_non_zero <= max_non_zero); 40 return .{ 41 .bitmap = bitmap, 42 .min_non_zero = min_non_zero, 43 .max_non_zero = max_non_zero, 44 }; 45 } 46 47 test "bitmap basic functionality" { 48 const allocator = std.testing.allocator; 49 const data = [_]u16{ 3, 10, 5, 1 }; 50 const result = try generateBitmap(&data, allocator); 51 defer allocator.free(result.bitmap); 52 try expect(result.min_non_zero == 0); 53 try expect(result.max_non_zero == 1); 54 // 1 -> byte 0, bit 1 (00000010) 55 // 3 -> byte 0, bit 3 (00001000) 56 // 5 -> byte 0, bit 5 (00100000) 57 // 10 -> byte 1, bit 2 (00000100) 58 try expect(result.bitmap[0] == 0b00101010); 59 try expect(result.bitmap[1] == 0b00000100); 60 for (result.bitmap[2..]) |byte| { 61 try expect(byte == 0); 62 } 63 } 64 65 test "bitmap largest value" { 66 const allocator = std.testing.allocator; 67 const data = [_]u16{u16_range - 1}; 68 const result = try generateBitmap(&data, allocator); 69 defer allocator.free(result.bitmap); 70 try expect(result.min_non_zero == bitmap_size - 1); 71 try expect(result.max_non_zero == bitmap_size - 1); 72 try expect(result.bitmap[bitmap_size - 1] == 0b10000000); 73 for (result.bitmap[0 .. bitmap_size - 1]) |byte| { 74 try expect(byte == 0); 75 } 76 } 77 78 const LUTResult = struct { 79 lut: []u16, 80 num_nonzero_values: u16, 81 }; 82 83 fn generateForwardLUT( 84 bitmap: []const u8, 85 allocator: std.mem.Allocator, 86 ) !LUTResult { 87 // Map values of set bits to sequential numbers. 88 const lut = try allocator.alloc(u16, u16_range); 89 @memset(lut, 0); 90 var k: u16 = 0; 91 for (0..u16_range) |i| { 92 const mask = @as(u8, 1) << @as(u3, @intCast(i & 7)); 93 const bit_is_set = (bitmap[i >> 3] & mask) != 0; 94 if ((i == 0) or bit_is_set) { 95 lut[i] = k; 96 k += 1; 97 } 98 } 99 return .{ .lut = lut, .num_nonzero_values = k - 1 }; 100 } 101 102 test "forward LUT" { 103 const allocator = std.testing.allocator; 104 const data = [_]u16{ 3, 10, 5, 1 }; 105 const bitmap = (try generateBitmap(&data, allocator)).bitmap; 106 defer allocator.free(bitmap); 107 const result = try generateForwardLUT(bitmap, allocator); 108 defer allocator.free(result.lut); 109 try expect(result.num_nonzero_values == 4); 110 try expect(result.lut[0] == 0); 111 try expect(result.lut[1] == 1); 112 try expect(result.lut[3] == 2); 113 try expect(result.lut[5] == 3); 114 try expect(result.lut[10] == 4); 115 } 116 117 fn generateReverseLUT( 118 bitmap: []const u8, 119 allocator: std.mem.Allocator, 120 ) !LUTResult { 121 // Map sequential numbers to values of set bits. 122 const lut = try allocator.alloc(u16, u16_range); 123 @memset(lut, 0); 124 var k: u16 = 0; 125 for (0..u16_range) |i| { 126 const mask = @as(u8, 1) << @as(u3, @intCast(i & 7)); 127 const bit_is_set = (bitmap[i >> 3] & mask) != 0; 128 if ((i == 0) or bit_is_set) { 129 lut[k] = @intCast(i); 130 k += 1; 131 } 132 } 133 return .{ .lut = lut, .num_nonzero_values = k - 1 }; 134 } 135 136 test "reverse LUT" { 137 const allocator = std.testing.allocator; 138 const data = [_]u16{ 3, 10, 5, 1 }; 139 const bitmap = (try generateBitmap(&data, allocator)).bitmap; 140 defer allocator.free(bitmap); 141 const result = try generateReverseLUT(bitmap, allocator); 142 defer allocator.free(result.lut); 143 try expect(result.num_nonzero_values == 4); 144 try expect(result.lut[0] == 0); 145 try expect(result.lut[1] == 1); 146 try expect(result.lut[2] == 3); 147 try expect(result.lut[3] == 5); 148 try expect(result.lut[4] == 10); 149 } 150 151 fn applyLUT(lut: []const u16, data: []u16) void { 152 for (data) |*x| { 153 x.* = lut[x.*]; 154 } 155 } 156 157 test "apply forward LUT" { 158 const allocator = std.testing.allocator; 159 var data = [_]u16{ 3, 10, 5, 1 }; 160 const bitmap = (try generateBitmap(&data, allocator)).bitmap; 161 defer allocator.free(bitmap); 162 const lut = (try generateForwardLUT(bitmap, allocator)).lut; 163 defer allocator.free(lut); 164 applyLUT(lut, &data); 165 try expect(data[0] == 2); 166 try expect(data[1] == 4); 167 try expect(data[2] == 3); 168 try expect(data[3] == 1); 169 } 170 171 test "apply LUTs roundtrip" { 172 const allocator = std.testing.allocator; 173 var data = [_]u16{ 3, 10, 5, 1 }; 174 const bitmap = (try generateBitmap(&data, allocator)).bitmap; 175 defer allocator.free(bitmap); 176 const forward_lut = (try generateForwardLUT(bitmap, allocator)).lut; 177 defer allocator.free(forward_lut); 178 const reverse_lut = (try generateReverseLUT(bitmap, allocator)).lut; 179 defer allocator.free(reverse_lut); 180 applyLUT(forward_lut, &data); 181 applyLUT(reverse_lut, &data); 182 try expect(data[0] == 3); 183 try expect(data[1] == 10); 184 try expect(data[2] == 5); 185 try expect(data[3] == 1); 186 }