17

我正在写一篇关于 Guid/UID 的人类可读替代品的小文章,例如在 TinyURL 上用于 url 哈希的那些(通常印在杂志上,所以需要简短)。

我生成的简单 uid 是 - 6 个字符:小写字母 (az) 或 0-9。

“根据我的计算队长”,这是 6 个相互排斥的事件,虽然计算冲突的概率比 P(A 或 B) = P(A) + P(B) 更难,因为显然它包括数字和从在下面的代码中,您可以看到使用 50/50 是使用数字还是字母。

我对冲突率感兴趣,如果下面的代码是您从生成哈希中获得的预期冲突率的真实模拟。平均而言,我每百万次发生 40-50 次冲突,但请记住,uid 不会一次生成一百万次,但可能每分钟只有 10-1000 次左右。

每次发生冲突的概率是多少,有人能提出更好的方法吗?

static Random _random = new Random();

public static void main()
{
    // Size of the key, 6
    HashSet<string> set = new HashSet<string>();
    int clashes = 0;
    for (int n=0;n < 1000000;n++)
    {
        StringBuilder builder = new StringBuilder();

        for (int i =0;i < 7;i++)
        {
            if (_random.NextDouble() > 0.5)
            {
                builder.Append((char)_random.Next(97,123));
            }
            else
            {
                builder.Append(_random.Next(0,9).ToString());
            }
        }

        if (set.Contains(builder.ToString()))
        {
            clashes++;
            Console.WriteLine("clash: (" +n+ ")" +builder.ToString());
        }

        set.Add(builder.ToString());
        _random.Next();
        //Console.Write(builder.ToString());
    }

    Console.WriteLine("Clashes: " +clashes);
    Console.ReadLine();
}

更新: 这是这个问题的结果文章

我真的在这里问了两个问题,所以我在作弊。我所追求的答案是 rcar 的,但 Sklivvz 的也是第二部分的答案(另一种选择)。是否可以在数据库中创建一个自定义的唯一 ID 生成器,或者它是客户端(首先是 2 个可能的读取)?

我所追求的一般想法是在数据库或其他可以通过电话或印刷材料使用的商店中使用 ID,而不是巨大的 16 字节 guid。

更新 2:我将公式用于两个互斥事件而不是 2 个独立事件(因为第一次获得“a”并不意味着您第二次无法获得“a”)。应该是 P(A 和 B) = P(A) x P(B)

4

8 回答 8

31

为什么要使用随机函数?我一直认为 tinyurl 使用顺序 ID 的 base 62 (0-9A-Za-z) 表示。没有冲突,网址总是尽可能短。

你会有一个像这样的数据库表

Id  URL
 1  http://google.com
 2  ...
... ...
156 ...
... ...

相应的 URL 将是:

http://example.com/1
http://example.com/2
...
http://example.com/2W
...
于 2008-10-10T10:20:18.773 回答
6

查找生日悖论,这正是您遇到的问题。

问题是:你需要多少人聚在一个房间里,这样你就有 50% 的机会让任何两个人的生日相同?答案可能会让你大吃一惊。

于 2008-10-10T10:18:41.960 回答
5

前段时间我正是这样做的,我遵循了 Sklivvz 提到的方式。整个逻辑是使用 SQL 服务器存储过程和几个 UDF(用户定义函数)开发的。步骤是:

  • 说你想缩短这个网址:Creating your own Tinyurl style uid
  • 在表格中插入 URL
  • 获取最后插入的@@identity 值(一个数字id)
  • 根据字母和数字的“域”将 id 转换为相应的字母数字值(我实际上使用了这个集合:“0123456789abcdefghijklmnopqrstuvwxyz”)
  • 返回该值,例如'cc0'

转换是通过几个非常短的 UDF 实现的。

一个接一个调用的两个转换将返回“顺序”值,如下所示:

select dbo.FX_CONV (123456) -- returns "1f5n"

select dbo.FX_CONV (123457) -- returns "1f5o"

如果您有兴趣,我可以分享 UDF 的代码。

于 2008-10-10T13:01:40.933 回答
4

与一个特定 ID 发生冲突的概率为:

p = ( 0.5 * ( (0.5*1/10) + (0.5*1/26) ) )^6

大约是 1.7×10^-9。

生成 n 个 ID 后发生冲突的概率为 1-p^n,因此在插入 100 万个 ID 后,每次新插入的冲突概率约为 0.17%,在插入 1000 万个 ID 后约为 1.7%,并且1亿后约16%。

1000 个 ID/分钟相当于每月 4300 万个,因此正如 Sklivvz 指出的那样,在这种情况下,使用一些递增的 ID 可能是一种更好的方法。

编辑:

为了解释数学,他基本上是在掷硬币,然后选择一个数字或字母 6 次。硬币翻转匹配的概率为 0.5,然后 50% 的时间有 1/10 的匹配机会和 50% 的匹配机会 1/26 的机会。这种情况独立发生了 6 次,因此您将这些概率相乘。

于 2008-10-10T10:37:55.463 回答
0

为什么不只使用散列算法?并使用网址的哈希?

如果您使用随机数,您可能会因为不确定而发生冲突。

哈希值不能证明是唯一的,但字符串的哈希值很有可能是唯一的。

更正

实际上等一下,您希望它们具有人类可读性......如果您将它们放在十六进制中,它们在技术上是人类可读的。

或者您可以使用将哈希转换为人类可读字符串的算法。如果人类可读的字符串是散列的不同表示形式,它也应该与散列一样“唯一”,即原始散列的基数 36。

于 2008-10-10T10:18:52.427 回答
0

我将生成一个代表您要散列的数据的随机值,然后对其进行散列并检查分类,而不是尝试使用随机手动生成的散列进行模拟。这会给你一个更好的指标。而且您将拥有更多的随机性,因为您将有更多的随机性(假设要散列的数据更大:))。

于 2008-10-10T10:27:54.483 回答
0

如果您使用 6 个字符,az 和 0-9,则总共有 36 个字符。因此排列的数量是 36^6,即 2176782336.. 所以它应该只冲突 1/2176782336 次。

于 2008-10-10T10:32:59.653 回答
0

来自维基百科

当需要打印更少的字符时,有时会将 GUID 编码为 base64 或 Ascii85 字符串。Base64 编码的 GUID 由 22 到 24 个字符组成(取决于填充),例如:

7QDBkvCA1+B9K/U0vrQx1A
7QDBkvCA1+B9K/U0vrQx1A==

而 Ascii85 编码只给出 20 个字符,例如:

5:$Hj:Pf\4RLB9%kU\Lj 

因此,如果您关心唯一性,base64 编码的 GUID 可以让您更接近您想要的,尽管它不是 6 个字符。

最好先以字节为单位,然后将这些字节转换为十六进制进行显示,而不是直接使用字符。

于 2008-10-10T10:34:56.140 回答