【问题标题】:Creating your own Tinyurl style uid创建自己的 Tinyurl 样式 uid
【发布时间】:2010-09-16 11:36:23
【问题描述】:

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

我生成的简单 uid 是 - 6 个字符:小写字母 (a-z) 或 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();
}

更新: Here's the resulting article来自这个问题

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

我的总体思路是在数据库或其他可通过电话或印刷材料使用的商店中使用 ID,而不是巨大的 16 字节 guid。

更新 2: 我将两个互斥事件的公式放在上面,而不是 2 个独立的事件(因为第一次得到 'a' 并不意味着你不能得到 'a '第二次)。应该是 P(A 和 B) = P(A) x P(B)

【问题讨论】:

    标签: c# algorithm probability


    【解决方案1】:

    为什么要使用随机函数?我一直认为 tinyurl 使用顺序 ID 的基数 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
    ...
    

    【讨论】:

    • 我认为这只是模拟它,而不是实际的哈希算法。这样他就可以确定指标,例如冲突率。
    • 直到现在我才听说过base62!看起来就像他们这样做的确切方式,可能是从 base62 解码,而不是存储密钥的 base62 版本,如您上面所说。
    • 我怀疑 TinyURL 使用基数 36 或至少基数 N,其中 N = 36。我认为他们不允许您同时使用小写和大写字符。如果您希望在某人通过电话口述时易于输入 URL,则不需要区分大小写的序列。
    • @Skliwz:您将如何处理同一天的第 9999999 个 ID?可能是 999....月底的第 9 个 ID?这意味着为什么我希望我的网址看起来像:http://site.com/999999999...999999w
    【解决方案2】:

    前段时间我正是这样做的,我按照 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 的代码。

    【讨论】:

    • 这就是为什么没有人应该问“我应该分享代码吗?”他们应该只分享代码。 5 年多了,KMan 还在等待。
    【解决方案3】:

    与一个特定 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 万个后约为 1.7% ID 数,1 亿后约为 16%。

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

    编辑:

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

    【讨论】:

    • 哈希 ID 的坏主意 - 您需要返回未哈希的 ID 以查找该行。请参阅 Sklivvz 的回答。
    • 我认为你的数学不太对。 OP 的数据表明每百万次碰撞约 50 次,而您预测为 1700 次(1000000 次的 0.17%)。也许我错过了什么?
    • 我不是指实际的哈希值;我只是想遵循 Sklivvz 的回答。我将编辑我的答案以澄清这一点。
    • 在 1,000,000 之后的下一次插入发生碰撞的概率是 0.17%,而不是之前的每次碰撞。正如我所说,碰撞的概率不是恒定的。这取决于已使用的 ID 数量。
    • 看起来如果你去掉 0-9 因素并去掉互斥部分,发生冲突的几率会低很多。也许只是使用 0-9 的 ascii 代码。这将是 1/26 x 1/26 x 1/26 x 1/26 x 1/26 x 1/26 = 0.038^6。上面的 0.5*1/26 是多少?
    【解决方案4】:

    来自wikipedia

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

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

    而 Ascii85 编码仅给出 20 个字符,例如。 g.:

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

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 是的,因为字符分布不均匀。考虑一个(非常糟糕的)算法:如果您掷出 D100 并且结果正好是 42,则生成一个非常长的 GUID。否则,结果 =“Doh”。可能的哈希数:大量。冲突的机会?巨大的!
      • @Jon:任何合理的哈希算法都会有一个均匀分布。 @Ryan:如果问题是“如果我生成了 2 个哈希值,它们碰撞的概率是多少?”,那么您的计算是正确的。但是这里的问题保留了以前生成的值,所以数学不是那么简单。
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

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

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

        更正

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

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

        【讨论】:

        • 我认为这只是模拟它,而不是实际的散列算法。这样他就可以确定指标,例如冲突率。
        【解决方案8】:

        查找Birthday Paradox,这正是您遇到的问题。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-09-27
          • 1970-01-01
          • 2016-09-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-27
          相关资源
          最近更新 更多