1 /** 2 * ae.utils.math.mixed_radix_coding 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.math.mixed_radix; 15 16 // TODO: Find what this thing is actually called. 17 /// A mixed-radix number coding system. 18 template MixedRadixCoder( 19 /// Numeric type for decoded items. 20 I, 21 /// Numeric type for encoded result. 22 E, 23 /// Use an encoding with an explicit end of items. 24 bool withEOF = false, 25 ) 26 { 27 /// This encoding system is LIFO, so the encoder buffers all items 28 /// until `.finish` is called. 29 struct Encoder( 30 /// Maximum number of encoded items. 31 size_t maxItems, 32 ) 33 { 34 private: 35 struct Item { I n, card; } 36 MaybeDynamicArray!(Item, maxItems) items; 37 38 public: 39 /// Encode one item. 40 /// `card` is the item's cardinality, i.e. one past its maximum. 41 void put(I n, I card) 42 in(0 <= n && n < card) 43 { 44 items ~= Item(n, card); 45 } 46 47 /// Finish encoding and return the encoded result. 48 E finish() 49 { 50 E result = withEOF ? 1 : 0; 51 foreach_reverse (ref item; items) 52 { 53 result *= item.card; 54 result += item.n; 55 } 56 return result; 57 } 58 } 59 60 /// As above. This will allocate the items dynamically. 61 alias VariableLengthEncoder = Encoder!(-1); 62 63 /// Like `Encoder`, but does not use a temporary buffer. 64 /// Instead, the user is expected to put the items in reverse order. 65 struct RetroEncoder 66 { 67 private: 68 E encoded = withEOF ? 1 : 0; 69 70 public: 71 void put(I n, I card) 72 { 73 assert(0 <= n && n < card); 74 encoded *= card; 75 encoded += n; 76 } /// 77 78 E finish() 79 { 80 return encoded; 81 } /// 82 } 83 84 /// The decoder. 85 struct Decoder 86 { 87 private: 88 E encoded; 89 90 public: 91 this(E encoded) 92 { 93 this.encoded = encoded; 94 static if (withEOF) 95 assert(encoded > 0); 96 } /// 97 98 I get(I card) 99 in(card > 0) 100 { 101 I value = encoded % card; 102 encoded /= card; 103 static if (withEOF) 104 assert(encoded > 0, "Decoding error"); 105 return value; 106 } /// 107 108 static if (withEOF) 109 @property bool empty() const { return encoded == 1; } /// 110 } 111 } 112 113 /// 114 unittest 115 { 116 alias Coder = MixedRadixCoder!(uint, uint, true); 117 Coder.Encoder!2 encoder; 118 encoder.put(5, 8); 119 encoder.put(1, 2); 120 auto result = encoder.finish(); 121 122 auto decoder = Coder.Decoder(result); 123 assert(decoder.get(8) == 5); 124 assert(decoder.get(2) == 1); 125 assert(decoder.empty); 126 } 127 128 unittest 129 { 130 import std.meta : AliasSeq; 131 import std.traits : EnumMembers; 132 import std.exception : assertThrown; 133 import core.exception : AssertError; 134 135 alias I = uint; 136 alias E = uint; 137 138 enum Mode { dynamicSize, staticSize, retro } 139 foreach (mode; EnumMembers!Mode) 140 foreach (withEOF; AliasSeq!(false, true)) 141 { 142 void testImpl() 143 { 144 alias Coder = MixedRadixCoder!(I, E, withEOF); 145 146 static if (mode == Mode.retro) 147 { 148 Coder.RetroEncoder encoder; 149 encoder.put(1, 2); 150 encoder.put(5, 8); 151 } 152 else 153 { 154 static if (mode == Mode.dynamicSize) 155 Coder.VariableLengthEncoder encoder; 156 else 157 Coder.Encoder!2 encoder; 158 159 encoder.put(5, 8); 160 encoder.put(1, 2); 161 } 162 auto result = encoder.finish(); 163 164 auto decoder = Coder.Decoder(result); 165 static if (withEOF) assert(!decoder.empty); 166 assert(decoder.get(8) == 5); 167 static if (withEOF) assert(!decoder.empty); 168 assert(decoder.get(2) == 1); 169 static if (withEOF) assert(decoder.empty); 170 171 static if (withEOF) 172 { 173 debug assertThrown!AssertError(decoder.get(42)); 174 } 175 else 176 assert(decoder.get(42) == 0); 177 } 178 static if (mode == Mode.dynamicSize) 179 testImpl(); 180 else 181 { 182 @nogc void test() { testImpl(); } 183 test(); 184 } 185 } 186 } 187 188 /// Serializes structs and static arrays using a `MixedRadixCoder`. 189 /// Consults types' `.max' property to obtain cardinality. 190 template SerializationCoder(alias Coder, S) 191 { 192 private alias I = typeof(Coder.Decoder.init.get(0)); 193 private alias E = typeof(Coder.RetroEncoder.init.finish()); 194 195 private struct Serializer 196 { 197 Coder.RetroEncoder encoder; 198 199 void put(T)(auto ref const T value) 200 { 201 static if (is(T : I) && is(typeof(T.max) : I)) 202 { 203 I max = T.max; 204 I card = max; card++; 205 assert(card > max, "Overflow"); 206 encoder.put(value, card); 207 } 208 else 209 static if (is(T == struct)) 210 foreach_reverse (const ref field; value.tupleof) 211 put(field); 212 else 213 static if (is(T == Q[N], Q, size_t N)) 214 foreach_reverse (ref item; value) 215 put(item); 216 else 217 static assert(false, "Don't know how to serialize " ~ T.stringof); 218 } 219 } 220 221 E serialize()(auto ref const S s) 222 { 223 Serializer serializer; 224 serializer.put(s); 225 return serializer.encoder.finish(); 226 } /// 227 228 private struct Deserializer 229 { 230 Coder.Decoder decoder; 231 232 void get(T)(ref T value) 233 { 234 static if (is(T : I) && is(typeof(T.max) : I)) 235 { 236 I max = T.max; 237 I card = max; card++; 238 assert(card > max, "Overflow"); 239 value = cast(T)decoder.get(card); 240 } 241 else 242 static if (is(T == struct)) 243 foreach (ref field; value.tupleof) 244 get(field); 245 else 246 static if (is(T == Q[N], Q, size_t N)) 247 foreach (ref item; value) 248 get(item); 249 else 250 static assert(false, "Don't know how to deserialize " ~ T.stringof); 251 } 252 } 253 254 S deserialize(E encoded) 255 { 256 Deserializer deserializer; 257 deserializer.decoder = Coder.Decoder(encoded); 258 S result; 259 deserializer.get(result); 260 static if (__traits(hasMember, deserializer.decoder, "empty")) 261 assert(deserializer.decoder.empty); 262 return result; 263 } /// 264 } 265 266 /// 267 unittest 268 { 269 static struct WithMax(T, T max_) 270 { 271 T value; 272 alias value this; 273 enum T max = max_; 274 } 275 276 enum E { a, b, c } 277 alias D6 = WithMax!(uint, 6); 278 279 static struct S 280 { 281 ubyte a; 282 bool b; 283 E[3] e; 284 D6 d; 285 } 286 287 alias Coder = SerializationCoder!(MixedRadixCoder!(uint, ulong, true), S); 288 auto s = S(1, true, [E.a, E.b, E.c], D6(4)); 289 assert(Coder.deserialize(Coder.serialize(s)) == s); 290 } 291 292 private struct MaybeDynamicArray(T, size_t size = -1) 293 { 294 static if (size == -1) 295 { 296 T[] items; 297 alias items this; 298 } 299 else 300 { 301 T[size] items; 302 size_t length; 303 void opOpAssign(string op : "~")(T item) { items[length++] = item; } 304 T[] opSlice() { return items[0 .. length]; } 305 } 306 }