当我们了解数据结构时,是否有更有效的方法来压缩数字字符串?

Avr*_*oel 4 c# compression

我们有会员卡(类似于信用卡/借记卡,但由我们的定制代码处理,而不是通过与银行接口处理的卡)。我们需要在卡上存储交易数据,因为许多交易将使用离线设备进行,并且仅在下次在在线终端上点击该卡时才上传。

\n

卡存储空间如果有限(通常最大为 8Kb,除非您为非常智能的卡支付愚蠢的价格),所以我需要尽可能地压缩数据。

\n

我们的交易数据由三部分组成,所有部分都只涉及数字(即不涉及字母或特殊字符)...

\n
    \n
  • 日期/时间 - 格式yyMMddhhmmssfff
  • \n
  • 设备序列号 - 17 位数字
  • \n
  • 金额 - 以便士为单位,最大 \xc2\xa3999.99,即五位数字
  • \n
\n

如果将其表示为一串数字,则每笔交易有 37 个数字。

\n

我尝试使用System.IO.Compression(遵循本博客文章中的代码以及随附的 GitHub 存储库,未包含在此处,因为它是类的沼泽标准用法)中的算法。

\n

这给出了一些相当令人印象深刻的结果,使用最佳 Gzip 算法减少了大约 72%。

\n

然而,我想知道,鉴于我们对交易数据的形状有所了解,是否可以对此进行改进。例如,数据的日期/时间部分细分如下......

\n
    \n
  • 年份 - 这里没有太多限制
  • \n
  • 月份 - 只能是 1-12
  • \n
  • 天 - 只能是 1-31
  • \n
  • 小时 - 只能是 0-23
  • \n
  • 分钟和秒 - 只能是 0-59
  • \n
  • 毫秒 - 无限制
  • \n
\n

任何人对这些限制是否有助于我改进这种压缩发表评论。谢谢

\n

Dmi*_*nko 8

我们可以将数据压缩为118位(或15字节)。到目前为止,我们有范围:

  • 日期和时间:1 Jan 2000 0:0:0.000最多1 Jan 2100 0:0:0.0003_155_760_000_000毫秒
  • 序列号:1_000_000_000_000_000_000可能的数字
  • 金额:1_000_00以便士为单位

所以我们总共有:

double dt = (new DateTime(2100, 1, 1) - new DateTime(2000, 1, 1)).TotalMilliseconds;
double sn = 1_000_000_000_000_000_000L;
double amount = 1_000_00;

Console.Write(Math.Log2(dt * sn * amount));
Run Code Online (Sandbox Code Playgroud)

结果是117.925470...位,118位,因为我们不能部分使用位

编辑:压缩和解压缩例程:

private static byte[] MyCompress(DateTime date, long serial, decimal amount) {
  BigInteger ms = (long)(date - new DateTime(2000, 1, 1)).TotalMilliseconds;

  BigInteger value = 
    ms * 1_000_000_000_000_000_000L * 1_000_00 +
    (BigInteger)serial * 1_000_00 +
    (BigInteger)(amount * 100);

  byte[] result = new byte[15];

  for (int i = result.Length - 1; i >= 0; --i, value /= 256) 
    result[i] = (byte)(value % 256);

  return result;
}

private static (DateTime date, long serial, decimal amount) MyDecomress(byte[] data) {
  BigInteger value = data.Aggregate(BigInteger.Zero, (s, a) => s * 256 + a);

  BigInteger amount = value % 1_000_00;
  BigInteger serial = (value / 1_000_00) % 1_000_000_000_000_000_000L;
  BigInteger dt = value / 1_000_00 / 1_000_000_000_000_000_000L;

  return (
    new DateTime(2000, 1, 1).AddMilliseconds((double)dt),
    (long)serial,
    (decimal)amount / 100M
  );
}
Run Code Online (Sandbox Code Playgroud)

演示:

var data = MyCompress(new DateTime(2023, 1, 25, 21, 06, 45), 12345, 345.87m);

Console.WriteLine(string.Join(" ", data.Select(b => b.ToString("X2"))));

var back = MyDecomress(data);

Console.Write(back);
Run Code Online (Sandbox Code Playgroud)

输出:

00 0E 05 4C 23 D7 34 A8 BD E8 F7 CC 3D 95 80 BB
(25.01.2023 21:06:45, 12345, 345.87)
Run Code Online (Sandbox Code Playgroud)

小提琴

编辑:如果我们可以存储高达1/10秒(而不是毫秒)的日期和时间,我们只能使用14字节:

private static byte[] MyCompress(DateTime date, long serial, decimal amount) {
  BigInteger ms = (long)(date - new DateTime(2000, 1, 1)).TotalMilliseconds / 100;

  BigInteger value = 
    ms * 1_000_000_000_000_000_000L * 1_000_00 +
    (BigInteger)serial * 1_000_00 +
    (BigInteger)(amount * 100);

  byte[] result = new byte[14];

  for (int i = result.Length - 1; i >= 0; --i, value /= 256) 
    result[i] = (byte)(value % 256);

  return result;
}

private static (DateTime date, long serial, decimal amount) MyDecomress(byte[] data) {
  BigInteger value = data.Aggregate(BigInteger.Zero, (s, a) => s * 256 + a);

  BigInteger amount = value % 1_000_00;
  BigInteger serial = (value / 1_000_00) % 1_000_000_000_000_000_000L;
  BigInteger dt = value / 1_000_00 / 1_000_000_000_000_000_000L;

  return (
    new DateTime(2000, 1, 1).AddMilliseconds((double)dt * 100),
    (long)serial,
    (decimal)amount / 100M
  );
}
Run Code Online (Sandbox Code Playgroud)


Ker*_*van 5

解决方案#1(旧的,16 字节):

\n

您可以通过使用上述限制来保存两位数字(字节):

\n
    \n
  1. 合并month+daydayOfYear(000-365 )(为了保持一致,假设 2 月始终有 29 天);
  2. \n
  3. 合并hours+minutes+secondstimeInSeconds(00000-86399 )。
  4. \n
\n

请注意,您可能可以使用一些其他技术来减小字符串的大小。

\n

之后,您可以将字符串中的数字从 转换base 10base 256。因此你得到16 个字节而不是 37 个字节。没有数学证明,只是通过链接在代码中得到实际结果(输出在页面底部)。\n https://ideone.com/SMKb6S

\n

结果:

\n
initial: 39 999912312359599999999999999999999999999\nbase10: 37 9999365863999999999999999999999999999\nbase256: 16 [7, 133, 206, 204, 233, 237, 90, 213, 156, 154, 224, 34, 63, 255, 255, 255]\nbase62: 21 EC5zRr0FV71hggqe73b0J\n
Run Code Online (Sandbox Code Playgroud)\n

之后你可以尝试一些压缩方法。然而,正如评论中指出的,它可能不适用于少量数据。

\n

解决方案#2(15 字节):

\n

实际上,您最终可以得到15 个字节Dmitry Bychenko 在他的回答中使用了微秒而不是毫秒(我没有足够的声誉在评论中指出这一点)。 固定的。那么,128 years将会是4_047_667_200_000 milliseconds(或类似的东西)。

\n

所有数据都容纳在 15 个字节中,有些位甚至是空闲的。例如,您可以使用它们来增加最大金额。以下是Python中的计算:https://ideone.com/37Bie3

\n

结果:

\n
Target bytes: 15 (120 bits)\nYears: 64\n  Total bits: 120\n  Max amount: \xc2\xa341943.04 (22 bits, 5 free bits used)\nYears: 128\n  Total bits: 120\n  Max amount: \xc2\xa320971.52 (21 bits, 4 free bits used)\nYears: 256\n  Total bits: 120\n  Max amount: \xc2\xa310485.76 (20 bits, 3 free bits used)\nYears: 512\n  Total bits: 120\n  Max amount: \xc2\xa35242.88 (19 bits, 2 free bits used)\nYears: 1024\n  Total bits: 120\n  Max amount: \xc2\xa32621.44 (18 bits, 1 free bits used)\nYears: 2048\n  Total bits: 120\n  Max amount: \xc2\xa31310.72 (17 bits, 0 free bits used)\n
Run Code Online (Sandbox Code Playgroud)\n

编辑:对解决方案 #1 执行一些格式化,添加解决方案 #2。

\n