goo.gl或jsfiddle等网站如何生成网址代码?

时间:2012-04-24 14:21:07

标签: c# algorithm

我想生成一个代码,如goo.gljsfiddle网站(http://jsfiddle.net/XzKvP/)。

我尝试了不同的东西,这些东西给了我太大的guid,重复的字母数字代码等。

我认为我应该能够根据数据库表中的主键生成字母数字代码。这样它会不重复吗? PK是一个自动递增的整数1.但不确定应该如何完成。

我希望代码 看起来 随机,但 NOT 必须是。 例如,我 NOT 希望我的数据库中的项1234BCDE1235项为{{1} }}

示例:

注意网址BCDF如何与页面关联的唯一5个字符代码http://jsfiddle.net/XzKvP/。我希望能够生成相同类型的代码。

goo.gl也是这样做的:http://goo.gl/UEhtgXzKvP

这是怎么做到的?

3 个答案:

答案 0 :(得分:24)

基于随机子串的解决方案并不好,因为输出会发生冲突。它可能过早地发生(运气不好),并且最终会在生成的值列表变大时发生。碰撞变高的可能性甚至不大(见birthday attack)。

这个问题的好处是递增ID与其对应关系之间的pseudo random permutation将显示在URL中。这种技术保证了碰撞是不可能的,同时仍然会产生一个与输入空间一样小的输出空间。

<强>实施

我建议这个C#版本的Feistel cipher具有32位块,3轮和圆函数,其灵感来自伪随机生成器。

private static double RoundFunction(uint input)
{
    // Must be a function in the mathematical sense (x=y implies f(x)=f(y))
    // but it doesn't have to be reversible.
    // Must return a value between 0 and 1
    return ((1369 * input + 150889) % 714025) / 714025.0;
}

private static uint PermuteId(uint id)
{
    uint l1=(id>>16)&65535;
    uint r1=id&65535;
    uint l2, r2;
    for (int i = 0; i < 3; i++)
    {
        l2 = r1;
        r2 = l1 ^ (uint)(RoundFunction(r1) * 65535);
        l1 = l2;
        r1 = r2;
    }
    return ((r1 << 16) + l1);
}

表示base62字符串中的置换ID:

private static string GenerateCode(uint id)
{
    return ToBase62(PermuteId(id));
}

Base62函数与the previous answer相同,除了uint而不是int(否则必须重写这些函数以处理负值)

自定义算法

RoundFunction是算法的秘诀。您可以将其更改为非公开版本,可能包括密钥。 Feistel网络有两个非常好的属性:

  • 即使提供的RoundFunction不可逆,算法也保证PermuteId()将是数学意义上的排列(意味着零冲突)。

  • 即使轻微改变圆函数内的表达式,也会彻底改变最终输出值列表。

请注意,在圆形表达式中放置一些过于微不足道的东西会破坏伪随机效应,尽管它仍然可以在每个PermuteId输出的唯一性方面起作用。此外,在数学意义上不是函数的表达式将与算法不兼容,因此例如不允许涉及random()的任何内容。

<强>可逆性

在当前形式中,PermuteId函数是它自己的反转,这意味着:

PermuteId(PermuteId(id))==id

因此,如果程序生成一个短字符串,如果使用uint函数将其转换回FromBase62,并将其作为PermuteId()的输入,那将返回相应的初始ID。如果您没有用于存储[internal-ID / shortstring]关系的数据库,这非常酷:它们实际上并不需要存储!

制作更短的字符串

上述函数的范围是32位,即从0到2^32-1的大约40亿个值。要在base62中表示该范围,需要6个字符。

只有5个字符,我们希望能够代表最多62^5个值,这个值略低于10亿。如果输出字符串限制为5个字符,则应按如下方式调整代码:

  • 找到NN为偶数,2^N尽可能高,但低于62^5。那是28,所以适合62^5的实际输出范围将是2^28或大约2.68亿的值。

  • PermuteId
  • ,使用28/2=14l1的{​​{1}}位值而不是16位,同时注意不要忽略输入的单个位(必须小于2 ^ 28)。

  • r1的结果乘以16383而不是65535,以保持在14位范围内。

  • RoundFunction的末尾,重新组合PermuteIdr1以形成l1位值,而不是32位。

同样的方法可以应用于4个字符,输出范围为14+14=28,或约400万个值。

它看起来像什么

在上面的版本中,以id = 1开头的前10个字符串是:

cZ6ahF
3t5mM
xGNPN
dxwUdS
ej9SyV
cmbVG3
cOlRkc
bfCPOX
JDr8Q
eg7iuA

如果我在圆函数中做了一个微不足道的改变,那就变成了:

ey0LlY
ddy0ak
dDw3wm
bVuNbg
bKGX22
c0s5GZ
dfNMSp
ZySqE
cxKH4b
dNqMDA

答案 1 :(得分:10)

您可以将五个字母的代码视为base-62表示法中的数字:您的“数字”是26个小写字母和26个大写字母,以及0到9个数字(26 + 26 + 10)个数字。给定从0到62^5(等于916132832)(例如,您的主键)的数字,您可以按如下方式转换为五位数的基数-62:

private static char Base62Digit(int d) {
    if (d < 26) {
        return (char)('a'+d);
    } else if (d < 52) {
        return (char)('A'+d-26);
    } else if (d < 62) {
        return (char)('0'+d-52);
    } else {
        throw new ArgumentException("d");
    }
}

static string ToBase62(int n) {
    var res = "";
    while (n != 0) {
        res = Base62Digit(n%62) + res;
        n /= 62;
    }
    return res;
}

private static int Base62Decode(char c) {
    if (c >= '0' && c <= '9') {
        return 52 + c - '0';
    } else if (c >= 'A' && c <= 'Z') {
        return 26 + c - 'A';
    } else if (c >= 'a' && c <= 'z') {
        return c - 'a';
    } else {
        throw new ArgumentException("c");
    }
}

static int FromBase62(string s) {
    return s.Aggregate(0, (current, c) => current*62 + Base62Decode(c));
}

以下是如何生成加密强大的随机数(您需要添加对System.Security的引用):

private static readonly RNGCryptoServiceProvider crypto =
    new RNGCryptoServiceProvider();

private static int NextRandom() {
    var buf = new byte[4];
    crypto.GetBytes(buf);
    return buf.Aggregate(0, (p, v) => (p << 8) + v) & 0x3FFFFFFF;
}

答案 2 :(得分:3)

这就是我最终做的事情

(自DanielVérité回答以来更新):

class Program
{

    private static double RoundFunction(uint input)
    {
        // Must be a function in the mathematical sense (x=y implies f(x)=f(y))
        // but it doesn't have to be reversible.
        // Must return a value between 0 and 1
        return ((1369 * input + 150889) % 714025) / 714025.0;
    }
    private static char Base62Digit(uint d)
    {
        if (d < 26)
        {
            return (char)('a' + d);
        }
        else if (d < 52)
        {
            return (char)('A' + d - 26);
        }
        else if (d < 62)
        {
            return (char)('0' + d - 52);
        }
        else
        {
            throw new ArgumentException("d");
        }
    }
    private static string ToBase62(uint n)
    {
        var res = "";
        while (n != 0)
        {
            res = Base62Digit(n % 62) + res;
            n /= 62;
        }
        return res;
    }
    private static uint PermuteId(uint id)
    {
        uint l1 = (id >> 16) & 65535;
        uint r1 = id & 65535;
        uint l2, r2;
        for (int i = 0; i < 3; i++)
        {
            l2 = r1;
            r2 = l1 ^ (uint)(RoundFunction(r1) * 65535);
            l1 = l2;
            r1 = r2;
        }
        return ((r1 << 16) + l1);
    }


    private static string GenerateCode(uint id)
    {
        return ToBase62(PermuteId(id));
    }

    static void Main(string[] args)
    {

        Console.WriteLine("testing...");

            try
            {

                for (uint x = 1; x < 1000000; x += 1)
                {
                    Console.Write(GenerateCode(x) + ",");

                }

            }
            catch (Exception err)
            {
                Console.WriteLine("error: " + err.Message);
            }

        Console.WriteLine("");
        Console.WriteLine("Press 'Enter' to continue...");
        Console.Read();
    }
}