簡體   English   中英

如何創建包含數字和字母的唯一字符串而不會重復使用一次名稱

[英]How to create unique string containing numbers and letters without repeating name once used

我正在嘗試在 C# 中進行以下編碼挑戰:

管理機器人工廠設置。

當機器人離開工廠時,它沒有名字。

機器人第一次開機時,會隨機生成一個名字,格式為兩個大寫字母后跟三個數字,如RX837或BC811。

每隔一段時間,我們需要將機器人重置為出廠設置,這意味着它的名稱會被擦除。 下次您詢問時,該機器人將使用新的隨機名稱進行響應。

名稱必須是隨機的:它們不應遵循可預測的順序。 使用隨機名稱意味着存在沖突的風險。 您的解決方案必須確保每個現有機器人都有唯一的名稱。

我創建了一個 Robot 類,它通過了我的 8 個單元測試中的 7 個。 失敗的一個是:

[Fact]
public void Robot_names_are_unique()
{
    const int robotsCount = 10_000;
    var robots = new List<Robot>(robotsCount); // Needed to keep a reference to the robots as IDs of recycled robots may be re-issued
    var names = new HashSet<string>(robotsCount);
    for (int i = 0; i < robotsCount; i++) {
        var robot = new Robot();
        robots.Add(robot);
        Assert.True(names.Add(robot.Name));
        Assert.Matches(@"^[A-Z]{2}\d{3}$", robot.Name);
    }
}

我瀏覽了我的代碼,我相信問題是因為我正在生成隨機值,但在創建許多名稱時我不能確保這些值是唯一的。 這是我的課:

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

public class Robot
{
    Random random = new Random();
    Dictionary<string, bool> usedNames = new Dictionary<string, bool>();
    public Robot()
    {
        Name = RandomName();
    }

    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }


    public void Reset()
    {
        Name = RandomName();
    }

    private string RandomName()
    {
        Random rand = new Random();
        int nums = random.Next(000, 1000);
        var val = nums.ToString("000");
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        string letters = new string(Enumerable.Repeat(chars, 2)
            .Select(s => s[random.Next(s.Length)]).ToArray());
        string name = $"{letters}{val}";
        if (usedNames.ContainsKey(name))
        {
// Implement here or refactor with loop?
        }
        return name;
    }
}

但是,在查看我的代碼后,我覺得有更好的方法。 我認為該方法將涉及從頭到尾依次遍歷名稱中可能的數字和字母,以確保每個名稱都是唯一的。 我在正確的軌道上嗎? 我還能做什么更好?

我們只有

26 * 26 * 1000 == 676000

可能的名字。 讓我們將它們全部生成並shuffle 然后我們可以從一個接一個的names獲取下一個機器人名稱:

// Yates algorithm will be faster then ordering by random (here I've used Guid)
static string[] Names = Enumerable
  .Range(0, 26 * 26)
  .SelectMany(letters => Enumerable
     .Range(0, 1000)
     .Select(i => $"{(char)('A' + letters / 26)}{(char)('A' + letters % 26)}{i:000}"))
  .OrderBy(item => Guid.NewGuid())
  .ToArray();

static int currentIndex = -1;

// Interlocked: let's implement thread safe method 
static string NextName() => 
  Names[Interlocked.Increment(ref currentIndex) % Names.Length]; 

演示:

for (int i = 0; i < 10; ++i)
  Console.WriteLine(NextName());    

結果:(可能因工作站而異)

JQ393
GQ249
JZ370
OC621
GD309
CP822
DK698
AD610
XY300
WV698

編輯:如果我們想重用名稱(當機器人設置為出廠默認設置時刪除),我們可以使用Queue而不是數組:

static ConcurrentQueue<string> Names = new ConcurrentQueue<string>(Enumerable
  .Range(0, 26 * 26)
  .SelectMany(letters => Enumerable
     .Range(0, 1000)
     .Select(i => $"{(char)('A' + letters / 26)}{(char)('A' + letters % 26)}{i:000}"))
  .OrderBy(item => Guid.NewGuid()));
  
static string NextName() => Names.TryDequeue(out string result) ? result : "???";

static string ScrapName(string name) => Names.Enqueue(name);

static string ResetName(string oldName) {
  string newName = Names.TryDequeue(out string result) 
    ? result 
    : "???";

  if (!string.IsNullOrEmpty(oldName))
    Names.Enqueue(oldName);

  return newName; 
}

有很多可能的名字。 除非您計划擁有近 50 萬個機器人,否則一個好的解決方案是創建一個自定義的、可重復使用的生成器來跟蹤所有生成的名稱。

public class UniqueNameGenerator
{
   private readonly HashSet<string> generatedNames;
   private readonly Random generator;

   public UniqueNameGenerator(Random random = null)
   {
      this.generatedNames = new HashSet<string>();
      this.generator = random ?? new Random();
   }

   public string GenerateName()
   {
      string name;

      do
      {
          name = this.TryGenerateName();
      }
      while(this.generatedNames.Contains(name));

      this.generatedNames.Add(name);
      return name;
   }

   private string TryGenerateName()
   {
      var nameBuilder = new StringBuilder();

      nameBuilder.Append(this.PickRandomLetter('A', 'Z'));
      nameBuilder.Append(this.PickRandomLetter('A', 'Z'));
      nameBuilder.Append(this.PickRandomNumber(0, 1000));

      return nameBuilder.ToString();
   }

   private int PickRandomNumber(int min, int max)
   {
      return this.generator.Next(min, max + 1);
   }

   private char PickRandomLetter(char from, char to)
   {
      var letterIndex = this.generator.Next((int)from, (int)to);
      return (char)letterIndex;
   }
}

在 Robot 類中保留一個它的靜態實例,或者更好的是,創建一個RobotFactory來創建帶有RobotFactory的單個實例的機器人。

使用 random.choice 選擇 2 個隨機字符和 3 個隨機數

import random

def generate_license():
    letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    numbers = "0123456789"
    license = ""
    for i in range(2):
        license += random.choice(letters)
    for i in range(3):
        license += random.choice(numbers)
    return license

for i in range(30):
    print(generate_license())

輸出:

FD508 FI820 TY975 NR415 GD041 IK313 GR103 WR994 PL631 WT808 UV119 KO727 LK584 GM629 BM545 VX728 UN773 AM000 UW267 KE949 KW182 TL030 YW536 AF038 PQ493 TT153 NP626 JK151 WA536 OU825

一種選擇是創建一個類來生成名稱。 該類應該跟蹤已創建的名稱。 如果機器人數量不多,這種方法效果更好。

public class NameGenerator
{
    static HashSet<string> created = new HashSet<string>();
    static Random rand = new Random();
    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    public static string GetName()
    {
        if (created.Count == 676000) {
            // Throw an exception?
        }

        string name;
        do {
            name = $"{chars[rand.Next(chars.Length)]}{chars[rand.Next(chars.Length)]}{rand.Next(0, 1000):D3}";
        } while (!created.Add(name));
        return name;
    }

    public static void Reset() {
        created = new HashSet<string>();
    }
}

一些快速分析:

生成的 ID 數量 時間(秒) 上次創建時間(毫秒) 大約內存使用(MB)}
1,000 ~0 <1 0.05
10,000 0.005 <1 0.52
50,000 0.032 <1 2.4
100,000 0.078 <1 4.9
250,000 0.229 <1 11.1
500,000 0.626 <1 22.8
600,000 0.961 <1 25.1
625,000 1.143 <1 25.8
650,000 1.390 <1 26.3
676,000 5.386 293 38.5

顯然,一旦接近676,000限制,就會有很大的增加。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM