簡體   English   中英

當我們對數據結構有所了解時,是否有更有效的壓縮數字串的方法?

[英]Is there a more efficient way of compressing strings of digits when we know something about the structure of the data?

我們有會員卡(如信用卡/借記卡,但由我們的定制代碼處理,而不是通過與銀行接口處理的卡)。 我們需要將交易數據存儲在卡上,因為許多交易將使用離線設備進行,並且只有在下次在在線終端上刷卡時才會上傳。

如果卡存儲空間有限(通常最大 8Kb,除非你為非常智能的卡支付愚蠢的價格),所以我需要盡可能地壓縮數據。

我們的交易數據由三部分組成,所有部分都只涉及數字(即不包括字母或特殊字符)...

  • 日期/時間 - 格式yyMMddhhmmssfff
  • 設備序列號 - 17 位
  • 金額 - 以便士為單位,最高 999.99 英鎊,即五位數

將其表示為一串數字給出每筆交易 37 位數字。

我嘗試使用System.IO.Compression中的算法(遵循這篇博文中的代碼,以及隨附的 GitHub 存儲庫,此處未包括在內,因為它是類的標准用法)。

這給出了一些非常令人印象深刻的結果,使用最佳 Gzip 算法減少了大約 72%。

但是,我想知道是否有可能對此進行改進,因為我們對交易數據的形狀有所了解。 例如,數據的日期/時間部分分解如下...

  • 年 - 這里沒有那么多限制
  • 月 - 只能是 1-12
  • day - 只能是 1-31
  • 小時 - 只能是 0-23
  • 分和秒 - 只能是 0-59
  • 毫秒 - 無限制

任何人對這些限制是否有助於我改進這種壓縮有任何評論。 謝謝

我們可以將數據壓縮成118位(或15字節)。 到目前為止一切順利,我們有范圍:

  • 日期和時間: 1 Jan 2000 0:0:0.0001 Jan 2100 0:0:0.000 ,即3_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));

結果是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
  );
}

演示:

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);

Output:

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)

小提琴

編輯:如果我們可以將日期和時間存儲到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
  );
}

解決方案 #1(舊的,16 字節):

您可以使用上述限制保存兩位數(字節):

  1. month+day組合成dayOfYear ( 000-365 )(為了保持一致性,假設 2 月總是有 29 天);
  2. hours+minutes+seconds合並為timeInSeconds ( 00000-86399 )。

請注意,您可能還可以使用其他一些技術來減小字符串的大小。

在此之后,您可以將字符串中的數字從base 10轉換為base 256 因此你得到16 個字節而不是 37 個字節。沒有數學證明,只是通過鏈接在代碼中的實際結果(頁面底部的輸出)。 https://ideone.com/SMKb6S

結果:

initial: 39 999912312359599999999999999999999999999
base10: 37 9999365863999999999999999999999999999
base256: 16 [7, 133, 206, 204, 233, 237, 90, 213, 156, 154, 224, 34, 63, 255, 255, 255]
base62: 21 EC5zRr0FV71hggqe73b0J

在此之后你可以嘗試一些壓縮方法。 但是,如評論中所述,它可能不適用於少量數據。

解決方案 #2(15 字節):

實際上,您最終可以得到15 個字節 Dmitry Bychenko 在他的回答中使用微秒而不是毫秒(我沒有足夠的聲譽在評論中指出這一點)。 固定的。 因此, 128 years將是4_047_667_200_000 milliseconds (或類似時間)。

所有數據都在 15 個字節中,有些位甚至是空閑的。 例如,您可以使用它們來增加最大數量。 下面是Python中的計算: https://ideone.com/37Bie3

結果:

Target bytes: 15 (120 bits)
Years: 64
  Total bits: 120
  Max amount: £41943.04 (22 bits, 5 free bits used)
Years: 128
  Total bits: 120
  Max amount: £20971.52 (21 bits, 4 free bits used)
Years: 256
  Total bits: 120
  Max amount: £10485.76 (20 bits, 3 free bits used)
Years: 512
  Total bits: 120
  Max amount: £5242.88 (19 bits, 2 free bits used)
Years: 1024
  Total bits: 120
  Max amount: £2621.44 (18 bits, 1 free bits used)
Years: 2048
  Total bits: 120
  Max amount: £1310.72 (17 bits, 0 free bits used)

編輯:對解決方案 #1 執行一些格式化,添加解決方案 #2。

與其嘗試壓縮數據的文本版本,不如考慮您的數據並以更有效的格式存儲它。

日期可以以秒為單位存儲,因為 EPOCH 時間(EDIT)滴答 DateTime object 應該占用 8 個字節(無符號長)。

您的設備序列號也可以存儲在無符號長整數中,如果有任何前導 0,則可以假設它們始終是固定的 17 位數字。

您的金額可以存儲在 0 到 99999 范圍內的無符號整數中,並假設最后兩位數字在小數點后。

這為您提供了 8 + 8 + 4 = 20 字節的總大小。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM