我有一种情况,我需要创建成千上万的唯一数字。但是这些数字必须是9位数,不能包含任何0。我当前的方法是生成9位数(1-9)并将它们连接在一起,如果该数字不在列表中,则将其添加到其中。 E.g。
public void generateIdentifiers(int quantity)
{
uniqueIdentifiers = new List<string>(quantity);
while (this.uniqueIdentifiers.Count < quantity)
{
string id = string.Empty;
id += random.Next(1,10);
id += random.Next(1,10);
id += random.Next(1,10);
id += " ";
id += random.Next(1,10);
id += random.Next(1,10);
id += random.Next(1,10);
id += " ";
id += random.Next(1,10);
id += random.Next(1,10);
id += random.Next(1,10);
if (!this.uniqueIdentifiers.Contains(id))
{
this.uniqueIdentifiers.Add(id);
}
}
}
然而,由于越来越多的生成数字是重复的,因此在大约400,000时,该过程确实变慢了。我正在寻找一种更有效的方式来执行这个过程,任何帮助都会非常感激。
编辑: - 我正在生成这些 - http://www.nhs.uk/NHSEngland/thenhs/records/Pages/thenhsnumber.aspx
答案 0 :(得分:16)
正如其他人所提到的,使用HashSet<T>
代替List<T>
此外,使用StringBuilder而不是简单的字符串操作将获得另外25%。如果你可以使用数字而不是字符串,你就赢了,因为它只需要三分之一或四分之一的时间。
var quantity = 400000;
var uniqueIdentifiers = new HashSet<int>();
while (uniqueIdentifiers.Count < quantity)
{
int i=0;
i = i*10 + random.Next(1,10);
i = i*10 + random.Next(1,10);
i = i*10 + random.Next(1,10);
i = i*10 + random.Next(1,10);
i = i*10 + random.Next(1,10);
i = i*10 + random.Next(1,10);
i = i*10 + random.Next(1,10);
i = i*10 + random.Next(1,10);
i = i*10 + random.Next(1,10);
uniqueIdentifiers.Add(i);
}
我的机器上需要大约270毫秒才能获得400,000个数字,大约700毫秒需要1,000,000个数字。这甚至没有任何并行性。
由于使用HashSet<T>
而不是List<T>
,此算法在O(n)中运行,即持续时间将呈线性增长。因此,10,000,000个值大约需要7秒钟。
答案 1 :(得分:4)
这个建议可能会也可能不会受欢迎......这取决于人们的观点。因为你没有太具体地说明你需要它们的频率,频率或确切数字,我会建议采用蛮力方法。
我会产生十万个数字 - 不应该花很长时间,也许几秒钟?然后使用Parallel LINQ对它们执行Distinct()以消除重复项。然后使用另一个PLINQ查询对剩余部分运行正则表达式,以消除其中的任何零。然后取顶部 x 千。 (PLINQ非常适合翻阅像这样的大型任务)。如果需要,请冲洗并重复,直到满足您的需要为止。
在一台体面的机器上,只需要花费更长的时间来编写这个简单的功能,而不是运行它。当你说你实际上需要“成千上万”时,我还会查询为什么你有400K条目要测试?
答案 2 :(得分:4)
这里的诀窍是你只需要一万个唯一数字。从理论上讲,你可能有近9,0E + 08的可能性,但为什么要关心你是否需要这么多?
一旦你意识到你可以减少那些组合,那么创建足够的唯一数字很容易:
long[] numbers = { 1, 3, 5, 7 }; //note that we just take a few numbers, enough to create the number of combinations we might need
var list = (from i0 in numbers
from i1 in numbers
from i2 in numbers
from i3 in numbers
from i4 in numbers
from i5 in numbers
from i6 in numbers
from i7 in numbers
from i8 in numbers
from i9 in numbers
select i0 + i1 * 10 + i2 * 100 + i3 * 1000 + i4 * 10000 + i5 * 100000 + i6 * 1000000 + i7 * 10000000 + i8 * 100000000 + i9 * 1000000000).ToList();
此代码段会立即创建超过1,000,000个有效唯一编号的列表。
答案 3 :(得分:3)
尝试避免检查,确保始终选择一个唯一的号码:
static char[] base9 = "123456789".ToCharArray();
static string ConvertToBase9(int value) {
int num = 9;
char[] result = new char[9];
for (int i = 8; i >= 0; --i) {
result[i] = base9[value % num];
value = value / num;
}
return new string(result);
}
public static void generateIdentifiers(int quantity) {
var uniqueIdentifiers = new List<string>(quantity);
// we have 387420489 (9^9) possible numbers of 9 digits in base 9.
// if we choose a number that is prime to that we can easily get always
// unique numbers
Random random = new Random();
int inc = 386000000;
int seed = random.Next(0, 387420489);
while (uniqueIdentifiers.Count < quantity) {
uniqueIdentifiers.Add(ConvertToBase9(seed));
seed += inc;
seed %= 387420489;
}
}
我会尝试用小数字解释背后的想法......
假设您最多有7种可能的组合。我们选择一个数为7的数字,例如3,和随机起始号码,例如, 4。
在每一轮,我们将当前数字加3,然后我们得到模7的结果,所以我们得到这个序列:
4 - &gt; 4 + 3%7 = 0
0 - &gt; 0 + 3%7 = 3
3 - &gt; 3 + 3%7 = 6
6 - &gt; 6 + 6%7 = 5
通过这种方式,我们以非连续的方式生成从0到6的所有值。在我的例子中,我们正在做同样的事情,但是我们有9 ^ 9种可能的组合,并且作为数字素数,我选择386000000(你只需要避免3的倍数)。
然后,我拿起序列中的数字并将其转换为基数9.
我希望这很清楚:)
我在我的机器上进行了测试,生成400k的唯一值需要大约1秒钟。
答案 4 :(得分:2)
看看已发布的解决方案,我看起来相当基本。但是,它起作用,并产生大约1s的100万个值(11s中1000万个)。
public static void generateIdentifiers(int quantity)
{
HashSet<int> uniqueIdentifiers = new HashSet<int>();
while (uniqueIdentifiers.Count < quantity)
{
int value = random.Next(111111111, 999999999);
if (!value.ToString().Contains('0') && !uniqueIdentifiers.Contains(value))
uniqueIdentifiers.Add(value);
}
}
答案 5 :(得分:2)
Meybe这会更快:
//we can generate first number wich in 9 base system will be between 88888888 - 888888888
//we can't start from zero becouse it will couse the great amount of 1 digit at begining
int randNumber = random.Next((int)Math.Pow(9, 8) - 1, (int)Math.Pow(9, 9));
//no we change our number to 9 base, but we add 1 to each digit in our number
StringBuilder builder = new StringBuilder();
for (int i=(int)Math.Pow(9,8); i>0;i= i/9)
{
builder.Append(randNumber / i +1);
randNumber = randNumber % i;
}
id = builder.ToString();
答案 6 :(得分:1)
使用字符串数组或字符串构建器,wjile使用字符串添加。
更重要的是,你的代码效率不高,因为在生成许多id后,你的列表可能会保存新生成的id,因此while循环将运行超过你需要的数量。
使用for循环并从此循环生成您的id而不随机化。如果需要随机id,则再次使用for循环并生成超出需要的数量并给出生成间隔,并从该列表中随机选择您需要的数量。
使用下面的代码创建静态列表并在启动程序时填写它。我稍后会添加第二个代码来生成随机ID列表。 [我有点忙]
public static Random RANDOM = new Random();
public static List<int> randomNumbers = new List<int>();
public static List<string> randomStrings = new List<string>();
private void fillRandomNumbers()
{
int i = 100;
while (i < 1000)
{
if (i.ToString().Contains('0') == false)
{
randomNumbers.Add(i);
}
}
}
答案 7 :(得分:0)
我认为首先要使用StringBuilder而不是连接 - 你会惊喜地发现。 Antoher的东西 - 使用更有效的数据结构,例如HashSet&lt;&gt;或哈希表。
如果你可以放弃非常奇怪的要求而不是零 - 那么你当然可以只使用一个随机操作,然后按照你想要的方式格式化你得到的数字。
答案 8 :(得分:0)
我认为@slugster大致正确 - 虽然您可以运行两个并行进程,一个用于生成数字,另一个用于验证它们,并在验证时将它们添加到接受的数字列表中。一旦你有足够的信号,就表示原来的过程停止了。
将此与其他建议相结合 - 使用更有效和更合适的数据结构 - 您应该拥有可以接受的功能。
然而,为什么需要这样的数字的问题也很重要 - 这个要求似乎应该被分析。
答案 9 :(得分:0)
这样的东西?
public List<string> generateIdentifiers2(int quantity)
{
var uniqueIdentifiers = new List<string>(quantity);
while (uniqueIdentifiers.Count < quantity)
{
var sb = new StringBuilder();
sb.Append(random.Next(11, 100));
sb.Append(" ");
sb.Append(random.Next(11, 100));
sb.Append(" ");
sb.Append(random.Next(11, 100));
var id = sb.ToString();
id = new string(id.ToList().ConvertAll(x => x == '0' ? char.Parse(random.Next(1, 10).ToString()) : x).ToArray());
if (!uniqueIdentifiers.Contains(id))
{
uniqueIdentifiers.Add(id);
}
}
return uniqueIdentifiers;
}