将大数字(或字符串)压缩为小值

Posted

技术标签:

【中文标题】将大数字(或字符串)压缩为小值【英文标题】:Compressing big number (or string) to small value 【发布时间】:2010-11-24 05:10:18 【问题描述】:

我的 ASP.NET 页面有以下查询字符串参数:

…?IDs=1000000012,1000000021,1000000013,1000000022&...

这里的IDs 参数总是有数字隔开,在本例中为,。目前有 4 个数字,但通常它们介于 37 之间。

现在,我正在寻找将上面的每个大数转换为最小可能值的方法;专门压缩IDs查询字符串参数的值。欢迎压缩每个数字算法或压缩IDs查询字符串参数的整个值。

    编码或解码不是问题;只是压缩值IDs查询字符串参数。 为 IDs 创建一些唯一的小值,然后从某个数据源检索其值超出了范围。

有没有一种算法可以将这么大的数字压缩成小的值,或者将IDs查询字符串参数的值压缩在一起?

【问题讨论】:

这些数字的范围是多少?是否使用所有数字 (0-9),数字 2-8 是否始终为 0? 不是答案 - 但解决方案需要考虑压缩背后的基本原理?如果它在生成的页面中包含很多,答案几乎肯定是使用 gzip 压缩,它会为您压缩这个(和所有 html),比通过它管理的微压缩具有更好的性能。如果是为了提高用户输入网址的速度,那么答案需要考虑这一点。 > 是否使用所有数字 (0-9),数字 2-8 是否始终为 0 ?否 >如果它在生成的页面中包含很多,答案几乎肯定是使用 gzip 推荐页面上的所有链接的 href 为“MyServer.com/ShowSomething.aspx?IDs=1000000012,1000000021,1000000013,1000000022&...”问题是压缩 ID 参数 其他人认为他应该只从每个号码中-1000000000,并将其添加回服务器端:D。不过说真的,我认为没有理由这样做。一般来说,您应该实施一个更好的系统。你需要这个的确切原因是什么?你遇到了什么问题? @silky 这些数字是由第三方工具生成的唯一数字,由不同的数据库团队管理。正如我在帖子中所说,我想压缩 ID 参数的每个数字或值,以免查询字符串 URL 太大。 【参考方案1】:

您的数字基本上需要很大的空间,因为您使用以 10 为底的数字来表示它们。一个改进是使用基数 16(十六进制)。例如,您可以将 255(3 位)表示为 ff(2 位)。

您可以通过使用更大的数字基数来进一步了解该概念...所有作为有效查询字符串参数的字符集:

A-Z、a-z、0-9、'.'、'-'、'~'、'_'、'+'

这使您可以使用 67 个字符的基数(请参阅 Wikipedia on QueryString)。

查看this SO post 了解将基数 10 转换为任意基数的方法。

编辑:

在链接的 SO 帖子中,查看这部分:

string xx = IntToString(42, 
            new char[]  '0','1','2','3','4','5','6','7','8','9',
            'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
            'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x');

这几乎就是您所需要的。只需通过添加它缺少的几个字符来扩展它:

yz.-~_+

那篇文章缺少返回base 10的方法。我不会写它:-)但过程是这样的:

定义一个计数器,我将调用 TOTAL。

查看最右边的字符并找到它在数组中的位置。 TOTAL =(字符在数组中的位置) 示例:输入为 BA1。 TOTAL 现在是 1(因为“1”在数组中的位置 1)

现在查看第一个字符左边的下一个字符并找到它在数组中的位置。 TOTAL += 47 *(字符在数组中的位置) 示例:输入为 BA1。总计现在是 (47 * 11) + 1 = 518

现在查看前一个字符左侧的下一个字符,并找到它在数组中的位置。 TOTAL += 47 * 47 *(字符在数组中的位置) 示例:输入为 BA1。现在总计为 (47 * 47 * 10) + (47 * 11) + 1 = 243508

等等。

我建议您编写一个单元测试,将一堆以 10 为基数的数字转换为以 47 为基数,然后再返回以确保您的转换代码正常工作。

请注意您如何仅用 3 位基数 47 表示 6 位基数 10 数字 :-)

【讨论】:

谢谢 Eric J。如果我理解了,我应该使用更高的基数来转换它。如果是这样,您建议使用什么数字作为基础? “...所有有效查询字符串参数的字符集:”你能解释一下吗? Base64 被高度推荐,并且比 base 67 更安全! @Dave:我建议使用 Base 67,使用我在帖子中列出的字符。这些是允许在查询字符串参数中使用的字符,无需进行 URL 编码。看链接。它提供了从 10 进制到任意进制的 C# 源代码。我将编辑我的帖子以概述如何回到 10 进制。 @Eric 谢谢你,等待你的更新。此外,如果可能,请添加可能引起关注的详细信息,例如性能或最佳实践。 @Dave:更新中。这种方式的性能应该很好。与通过 Internet 调用 Web 服务器的时间相比,对数字进行编码的时间应该是微不足道的。【参考方案2】:

你的数字范围是多少?假设它们可以放入 16 位整数,我会:

将所有数字存储为 16-bit integers(每个数字 2 个字节,范围 -32,768 到 32,767) 构建一个 16 位整数的字节流(XDR 在这里可能是一个不错的选择;至少,请确保正确处理 endianness) Base64 对字节流进行编码,对 URL 使用修改后的 base64 编码(每个数字大约 3 个字符)

作为额外的好处,您不再需要逗号字符,因为您知道每个数字都是 2 个字节。

或者,如果这还不够好,我会使用zlib 压缩您的整数流,然后使用base64 压缩zlib 压缩流。如果 16 位的范围不够大(即,如果您确实需要 1,000,000,000 范围内的数字),您也可以切换到 32 位整数。

编辑:

也许为时已晚,但这里有一个可以满足您需要的实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Scratch 
    class Program 
        static void Main(string[] args) 
            //var ids = new[]  1000000012, 1000000021, 1000000013, 1000000022 ;
            var rand = new Random();
            var ids = new int[rand.Next(20)];
            for(var i = 0; i < ids.Length; i++) 
                ids[i] = rand.Next();
            

            WriteIds(ids);
            var s = IdsToString(ids);
            Console.WriteLine("\nResult string is: 0", s);
            var newIds = StringToIds(s);
            WriteIds(newIds);
            Console.ReadLine();
        

        public static void WriteIds(ICollection<Int32> ids) 
            Console.Write("\nIDs: ");
            bool comma = false;
            foreach(var id in ids) 
                if(comma) 
                    Console.Write(",");
                 else 
                    comma = true;
                
                Console.Write(id);
            
            Console.WriteLine();
        

        public static string IdsToString(ICollection<Int32> ids) 
            var allbytes = new List<byte>();
            foreach(var id in ids) 
                var bytes = BitConverter.GetBytes(id);
                allbytes.AddRange(bytes);                
            
            var str = Convert.ToBase64String(allbytes.ToArray(), Base64FormattingOptions.None);
            return str.Replace('+', '-').Replace('/', '_').Replace('=', '.');
        

        public static ICollection<Int32> StringToIds(string idstring) 
            var result = new List<Int32>();
            var str = idstring.Replace('-', '+').Replace('_', '/').Replace('.', '=');
            var bytes = Convert.FromBase64String(str);
            for(var i = 0; i < bytes.Length; i += 4) 
                var id = BitConverter.ToInt32(bytes, i);
                result.Add(id);
            
            return result;
        
    

【讨论】:

谢谢 Daniel,它的 C# 语言和数字可能是:1000000012,1000000021,1000000013,1000000022 87 个字符到 44 个字符 丹尼尔很棒。非常感谢。【参考方案3】:

这是另一个非常简单的方案,它可以很好地压缩N + delta 形式的一组数字,其中 N 是一个很大的常数。

public int[] compress(int[] input) 
    int[] res = input.clone();
    Arrays.sort(res);
    for (int i = 1; i < res.length; i++) 
        res[i] = res[i] - res[i - 1];
    
    return res;

这应该将集合1000000012,1000000021,1000000013,1000000022 减少到列表[1000000012,1,9,1],然后您可以通过表示base47 编码中的数字来进一步压缩,如另一个答案中所述。

使用简单的十进制编码,从 44 个字符变为 16 个字符;即 63%。 (并且使用 base47 将提供更多的压缩)。

如果对 id 进行排序是不可接受的,那么您的压缩效果就不会那么好。对于此示例,1000000012,1000000021,1000000013,1000000022 压缩到列表 [1000000012,9,-8,9]。对于这个例子来说,这只是长了一个字符

无论哪种方式,这都比通用压缩算法或编码方案要好......对于这种输入。

【讨论】:

@Mark: ...假设排序没问题,它可以处理一组数字中的多个 N 值,尽管每个新的 N 都会增加一个不可压缩量。跨度> 【参考方案4】:

如果唯一的问题是 URL 长度,您可以将数字转换为 base64 characters,然后在服务器端将它们转换回数字

【讨论】:

Base64 并不是真正的最佳选择,因为字符“+”、“/”和“=”都被使用了,并且它们将被 url 编码(使它们比必要的长得多)。 将字符串编码为 base64 编码将使它们更大而不是更小(在opinionatedgeek.com/dotnet/tools/Base64Encode/Default.aspx 尝试)。当您想以 ascii 形式表示二进制数据但不提供任何压缩时,Base64 编码很方便。 我的意思不是“将字符串转换为 base64”......我是说:“将数字转换为 base64”......即将数字的当前十进制表示转换为 base64 字符串,这应该压缩它们。但我同意 Eric J 的观点,有些字符不应该使用。 @Eric:你有没有看过 Aziz 提供的链接?它描述了“base64url”编码,避免了URL编码扩展。【参考方案5】:

您获得的 ID 的模式如何?如果逐个数字地,ID是随机的,那么我将要提出的方法不会很有效。但是,如果您作为示例提供的 ID 代表您将获得的类型,那么也许以下方法可行?

我通过例子来激发这个想法。

例如,您有 1000000012 作为您想要压缩的 ID。为什么不将其存储为 [1,0,7,12]?这意味着第一个数字是 1,后跟 7 个零,然后是 12。因此,如果我们使用表示 x 的一个实例的符号 x,而如果我们使用 x,y,则表示 x连续出现 y 次。

您可以通过一些模式匹配和/或函数拟合来扩展它。

例如,模式匹配:1000100032 将是 [1000,232]。

例如,函数拟合: 如果您的 ID 是 10 位数字,则将 ID 拆分为两个 5 位数字并存储通过这两个点的线的方程式。如果 ID = 1000000012,则 y1 = 10000 和 y2 = 12。因此,斜率为 -9988,截距为 10000(假设 x1 = 0,x2 = 1)。在这种情况下,这不是一种改进,但如果数字更随机,它可能是。等效地,您可以使用分段线性函数存储 ID 序列。

无论如何,这主要取决于您的 ID 结构。

【讨论】:

【参考方案6】:

我假设您这样做是为了解决请求 URL 长度限制...

其他答案建议使用十六进制、base47 或 base64 编码十进制 id 数字,但您可以(理论上)比使用 LZW(或类似的)压缩 id 列表做得更好。根据您的 ID 列表中有多少冗余,您可以显着减少 40% 以上,即使在将压缩字节重新编码为文本之后也是如此。

简而言之,我建议您找到一个用 javascript 实现的现成文本压缩库,并在客户端使用它来压缩 ID 列表。然后使用 base47/base64 对压缩后的字节串进行编码,并将编码后的字符串作为 URL 参数传递。在服务器端做相反的事情;即解码后解压缩。

编辑:作为实验,我创建了一个包含 36 个不同标识符的列表,就像您提供的标识符一样,并使用 gzip 对其进行压缩。原文件396字节,压缩文件101字节,压缩+base64文件138字节。总体上减少了 65%。对于较大的文件,压缩率实际上可以提高。然而,当我用一个小的输入集(例如,只有 4 个原始标识符)尝试这个时,我没有得到压缩,并且编码后的大小比原来的大。

谷歌“lzw 库 javascript”

理论上,可能有更简单的解决方案。将参数作为“发布数据”而不是在请求 URL 中发送,并让浏览器使用它理解的一种编码来应用压缩。由于无需将压缩数据编码为合法的 URL 字符,这也将为您节省更多费用。

问题在于让浏览器压缩请求......并以独立于浏览器的方式进行。

【讨论】:

以上是关于将大数字(或字符串)压缩为小值的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 将大数字乘以字符串

c_cpp 将大数字表示为字符串

将大查询数据导出到云存储,整数字段更改为字符串格式,但浮点格式保持为数字格式

sh 将大文件拆分为小文本文件(按行数)

空格和逗号哪个的ascii码值大

linux 将大文件分解为多个小文件