简体   繁体   English

如何在C#中创建一个独特的随机字符序列?

[英]How can I create an unique random sequence of characters in C#?

I'm implementing a URL shortening feature in my application in order to provide my users shorter alternative URLs that can be used in Twitter. 我正在我的应用程序中实现URL缩短功能,以便为我的用户提供可在Twitter中使用的更短的替代URL。 The point is to be independent from the shortening services that offer this same service and include it as a feature of my web app. 重点是独立于提供相同服务的缩短服务,并将其作为我的网络应用程序的一项功能。

What's the best way to create an unique random sequence of characters of about 6 chars? 创建一个大约6个字符的独特随机字符序列的最佳方法是什么? I plan to use that as an index for the items in my database that will have the alternative URLs. 我计划将其用作我的数据库中具有备用URL的项目的索引。

Edited: 编辑:

This feature will be used in a job board website, where every new job ad will get a custom URL with the title plus the shorter one to be used in Twitter. 此功能将用于工作板网站,其中每个新的工作广告都将获得带有标题的自定义URL以及要在Twitter中使用的较短的URL。 That said, the total number of unique 6 char combinations will be more than enough for a long time. 也就是说,长期以来,独特的6个字符组合的总数将绰绰有余。

The simplest way to make unique sequences is to do this sequentially, ie: aaaaaa aaaaab aaaaac ... These aren't necessarily the prettiest, but will guarantee uniqueness for the first 12230590463 sequences (provided you used az and AZ as unique characters). 制作独特序列的最简单方法是按顺序执行此操作,即:aaaaaa aaaaab aaaaac ...这些不一定是最漂亮的,但会保证第一个12230590463序列的唯一性(假设您使用az和AZ作为唯一字符)。 If you need more URLs than that, you'd need to add a seventh char. 如果您需要更多的URL,则需要添加第七个字符。

They aren't random sequences, though. 但它们不是随机序列。 If you make random ones, just pick a random char of the 48, 6 times. 如果你做随机的,只需选择48次,6次随机字符。 You'll need to check your existing DB for "used" sequences, though, as you'll be more likely to get collisions. 但是,您需要检查现有数据库中的“已使用”序列,因为您更有可能发生冲突。

Do you really need 'random', or would 'unique' be sufficient? 你真的需要'随机',还是'独特'就足够了?

Unique is extremely simple - just insert the URL into a database, and convert the sequential id for that record to a base-n number which is represented by your chosen characterset. Unique非常简单 - 只需将URL插入数据库,然后将该记录的顺序ID转换为base-n数字,该数字由您选择的characterset表示。

For example, if you want to only use [AZ] in your sequence, you convert the id of the record to a base 26 number, where A=1, B=2,... Z=26. 例如,如果您只想在序列中使用[AZ],则将记录的id转换为基数为26的数字,其中A = 1,B = 2,... Z = 26。 The algothithm is a recursive div26/mod26, where the quotient is the required character and the remainder is used to calculate the next character. algothithm是递归div26 / mod26,其中商是必需字符,余数用于计算下一个字符。

Then when retrieving URL, you perform the inverse function, which is to convert the base-26 number back to decimal. 然后在检索URL时,执行反函数,即将base-26数转换回十进制数。 Perform SELECT URL WHERE ID = decimal, and you're done! 执行SELECT URL WHERE ID = decimal,你就完成了!

EDIT: 编辑:

private string alphabet = "abcdefghijklmnopqrstuvwxyz"; 
   // or whatever you want.  Include more characters 
   // for more combinations and shorter URLs

public string Encode(int databaseId)
{
    string encodedValue = String.Empty;

    while (databaseId > encodingBase)
    {
        int remainder;
        encodedValue += alphabet[Math.DivRem(databaseId, alphabet.Length, 
            out remainder)-1].ToString();
        databaseId = remainder;
    }
    return encodedValue;
}

public int Decode(string code)
{
    int returnValue;

    for (int thisPosition = 0; thisPosition < code.Length; thisPosition++)
    {
        char thisCharacter = code[thisPosition];

        returnValue += alphabet.IndexOf(thisCharacter) * 
            Math.Pow(alphabet.Length, code.Length - thisPosition - 1);
    }
    return returnValue;
}

I would use an autonumber system, and create an algorithm to generate the keys. 我会使用自动编号系统,并创建一个算法来生成密钥。 ie 1 = a, 2 = b, 27 = aa etc. 即1 = a,2 = b,27 = aa等

You can use the database autonumber to guarantee that your URL is unique, and you can calculate the URL possibly in a sproc in the DB or in your business layer? 您可以使用数据库自​​动编号来保证您的URL是唯一的,并且您可以在数据库或业务层的sproc中计算URL?

Additionally you can now index on the incrementing number which is cheap and DB's are optimised for these to be used and hashed as primary/foreign keys as opposed to a variable length random string. 此外,您现在可以索引递增数字,该数字便宜,并且DB优化用于这些数字并且作为主键/外键进行散列,而不是可变长度随机字符串。

The usefulness of a random generator is limited to preventing users from plugging random URLs in to find things they shouldn't have a link to. 随机生成器的用处仅限于阻止用户插入随机URL以查找他们不应该链接的内容。 If this is not your goal then sequential IDs should work just fine. 如果这不是您的目标,那么顺序ID应该可以正常工作。 If you just don't want to give users the impression that they are using "infant" technology (when they see that their job ad is #000001), why not start the sequence at some arbitrary value? 如果您只是不想让用户觉得他们正在使用“婴儿”技术(当他们看到他们的工作广告是#000001时),为什么不以某个任意值启动序列呢?

When you state " total number of unique 6 char combinations will be more than enough for a long time " for your random generation have you factored the birthday paradox into your calculations? 当你指出“随机生成的长期独特的6个字符组合的总数将足够多 ”时,您是否将生日悖论计算在计算中? This is generally the bane of any attempt to create random IDs within a range that is only 1 order of magnitude or less then the expected range that will be needed. 这通常是在一个范围内创建随机ID的任何尝试的祸根,该范围仅为所需的预期范围的1个数量级或更少。

To create truly random IDs, you would need to create a loop that generates a new random value, checks to see if that value has already been used, and then repeats the loop if needed. 要创建真正的随机ID,您需要创建一个生成新随机值的循环,检查是否已使用该值,然后根据需要重复循环。 The birthday paradox means that you quickly get to the point where many of the values generated are already in use (despite only a fraction of the total range being consumed), which causes the program to get slower and slower over time until it is taking thousands of attempts (and database lookups) to generate each ID. 生日悖论意味着您很快就会到达生成的许多值已经被使用的点(尽管只消耗了总量程的一小部分),这会导致程序随着时间的推移变得越来越慢,直到数千万尝试(和数据库查找)生成每个ID。

I would suggest you go with the idea of encoding sequential IDs. 我建议你采用编码顺序ID的想法。 To avoid the problem of users being able to simply increment/decrement the value in the URL to "explore", you can use a combination bit shifting and an alternate ordered list of letters (instead of 1=a, 2=b use 1=t, 2=j, etc). 为了避免用户能够简单地将URL中的值递增/递减为“探索”的问题,您可以使用组合位移和备用的有序字母列表(而不是1 = a,2 = b使用1 = t,2 = j等)。

Thinking about this more here is an idea. 在这里考虑更多是一个想法。

You can start with a key table, incrementing chars AAAAAA - ZZZZZZ. 您可以从密钥表开始,递增字符AAAAAA - ZZZZZZ。

Then do a random select from that table each time you insert a new URL, and delete from the available keys. 然后在每次插入新URL时从该表中随机选择,并从可用键中删除。

Thoughts? 思考?

For random select try this link 对于随机选择尝试此链接

Select a random row with MySQL:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1
Select a random row with PostgreSQL:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1
Select a random row with Microsoft SQL Server:

SELECT TOP 1 column FROM table
ORDER BY NEWID()
Select a random row with IBM DB2

SELECT column, RAND() as IDX 
FROM table 
ORDER BY IDX FETCH FIRST 1 ROWS ONLY
Thanks Tim

Select a random record with Oracle:

SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1

Instead of keeping a table of all possible values, just keep a table of the values you've used. 不要保留所有可能值的表,只需保留一个您使用过的值的表。 Use the random function to generate 6 random values, 1 to 26, make the string from that and save it in an array or table. 使用随机函数生成6个随机值,1到26,从中生成字符串并将其保存在数组或表中。 If it already exists, you can (a) generate another string, or (b) move through the table to the next available (missing) 6-letter string and use that value. 如果它已经存在,您可以(a)生成另一个字符串,或(b)通过表移动到下一个可用(缺失)的6个字母的字符串并使用该值。 (b) will be more efficient as the table fills. (b)当桌子填满时会更有效率。

Following the idea of Reed Copsey's answer, i present the following code: 根据Reed Copsey的回答,我提出以下代码:

class IDGetter
{
    private StringID ID = new StringID();
    public string GetCurrentID()
    {
        string retStr = "";
        if (ID.char1 > 51)
            id.char1 = 0;
        if (ID.char2 > 51)
            id.char2 = 0;
        if (ID.char3 > 51)
            id.char3 = 0;
        if (ID.char4 > 51)
            id.char4 = 0;
        if (ID.char5 > 51)
            id.char5 = 0;
        if (ID.char6 > 51)
            throw new Exception("the maximum number of id's has been reached");
        return ToIDChar(ID.char1) + ToIDChar(ID.char2) + ToIDChar(ID.char3) + ToIDChar(ID.char4) + ToIDChar(ID.char5) + ToIDChar(ID.char6)
        id.char1++;
    }
    public void SetCurrentID(StringID id) //for setting the current ID from storage or resetting it or something
    {
        this.ID = id;
    }
    private const string alphabet = "abcdefghijklmopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static string ToIDChar(int number)
    {
        if (number > 51 || number < 0)
        {
            throw new InvalidArgumentException("the number passed in (" + number + ") must be between the range 0-51");
        }
        return alphabet[number];
    }
}
public struct StringID 
{
    public int char1 = 0;
    public int char2 = 0;
    public int char3 = 0;
    public int char4 = 0;
    public int char5 = 0;
    public int char6 = 0;
}

You might want to come up with a method of storing the current ID but that ought to work. 您可能想要提出一种存储当前ID的方法,但该方法应该可行。

I used this to do something very similar. 我用它来做一些非常相似的事情。 I was not to worried about the speed of it as it was going to be a rarely used event and table. 我不担心它的速度,因为它将是一个很少使用的事件和表。 But it's possible to then increase the string as needed. 但是可以根据需要增加字符串。

/// Generates a string and checks for existance
/// <returns>Non-existant string as ID</returns>
public static string GetRandomNumbers(int numChars, string Type)
{
    string result = string.Empty;
    bool isUnique = false;
    while (!isUnique)
    {
        //Build the string
        result = MakeID(numChars);
        //Check if unsued
        isUnique = GetValueExists(result, Type);
    }
    return result;
}
/// Builds the string
 public static string MakeID(int numChars)
{
    string random = string.Empty;
    string[] chars = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" };
    Random rnd = new Random();
    for (int i = 0; i < numChars; i++)
    {
        random += chars[rnd.Next(0, 35)];
    }
    return random;
}
/// Checks database tables based on type for existance, if exists then retry
/// <returns>true or false</returns>
private static bool GetValueExists(string value, string Type)
{
    bool result = false;
    string sql = "";
    if (Type == "URL")
    {
        sql = string.Format(@"IF EXISTS (SELECT COUNT(1) FROM myTable WHERE uniqueString = '{0}')
         BEGIN
             SELECT 1
         END
          ELSE
          BEGIN
             SELECT 0
         END ", value);
    }
    //query the DB to see if it's in use
    result = //ExecuteSQL
    return result;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM