C # random number
I am working on a project where I need to generate 8 random numbers. For some reason I have a random number part problem which is very time consuming. I mean 8 random numbers, I need a string of 8 characters long, consisting of digits 0-9. Example 01234567 or 23716253, etc.
I tried looping 8 times, generating a random number with Random.Next (0, 9) and just turning them into a string and concatenating them down to the last line. I also tried to generate a random number using Random.Next (0, 99999999) and just convert the number to a string and defer to 8 using 0.
Both seem to be pretty slow and I need to find a faster way. I don't mind making calls to other languages ββor whatever if it helps performance.
Here's some more information to add. I don't think they'll be able to find anything super effective. I have to generate this number about 50,000 times. When I ran the test with 47000 it took 8:39 seconds. It's only 0.011 seconds each time, but it just slowed down because im also working with a table. I also called hashtable.ContainsKey () a total of 47000 times and it only took 58 seconds. It's just such a big difference.
Here is the code that I used originally. Convert.ToString (rg.Next (0, 99999999)). PadLeft (8, '0');
Here's some code to try and figure it out. Here is the time I get Contains value: 00: 00: 00.4287102 Contains key: 00: 01: 12.2539062 Create key: 00: 08: 24.2832039 Add: 00:00:00
TimeSpan containsValue = new TimeSpan();
TimeSpan containsKey = new TimeSpan();
TimeSpan generateCode = new TimeSpan();
TimeSpan addCode = new TimeSpan();
StreamReader sr = new StreamReader(txtDictionaryFile.Text);
string curWord = sr.ReadLine().ToUpper();
int i = 1;
DateTime start;
DateTime end;
while (!sr.EndOfStream)
{
start = DateTime.Now;
bool exists = mCodeBook.ContainsValue(curWord);
end = DateTime.Now;
containsValue += end - start;
if (!exists)
{
string newCode;
bool kExists;
do
{
start = DateTime.Now;
Random rnd = new Random();
StringBuilder builder = new StringBuilder(8);
byte[] b = new byte[8];
rnd.NextBytes(b);
for (int i = 0; i < 8; i++)
{
builder.Append((char)((b[i] % 10) + 48));
}
newCode = builder.ToString();
end = DateTime.Now;
generateCode += end - start;
start = DateTime.Now;
kExists = mCodeBook.ContainsKey(newCode);
end = DateTime.Now;
containsKey += end - start;
}
while (kExists);
start = DateTime.Now;
mCodeBook.Add(newCode, curWord);
end = DateTime.Now;
addCode += start - end;
}
i++;
curWord = sr.ReadLine().ToUpper();
}
source to share
It is unlikely that the actual problem you are seeing is that random number generation is slow, but rather that you do it very often. As the number of items in your "codebook" increases, the likelihood of colliding with an existing number also increases. This will cause your ... while loop to keep executing over and over until it finds something available.
I don't know how much data you are loading into the codebook, but if it matters at all, you need to think about what will happen to the duplicate entries.
In your case, the problem is getting worse because you call "new Random ()" inside the loop. This causes the random number generator to be "reloaded" with a value derived from the current time (in fact, the total number of milliseconds since system boot). This means that any time you have a collision, the loop will immediately re-execute and pick exactly the same random seed as before, which will cause the "random" number you generate to match as well previously checked number. In fact, depending on the speed of your device, it can replay the same number of times.
The fastest workaround for this problem, which is consistent with the structure of your current code, is to simply remove all places where you call "new random" and have one random number generator at the beginning of the method that you are reusing. This makes sure it doesn't reset every time you go through the loop, and reduces the chances of creating the same number over and over.
To really fix this, you'll have to think more about the random numbers you use. Is it really important that the numbers you generate are random or they just have to be unique. Plus, you can generate large random numbers to reduce the chance of any duplicates. A large enough random number will completely eliminate the possibility of duplication. The extreme part of this would be using Guid.NewGuid (). ToString () to generate keys
Also as a side note. The performance metrics you show most likely don't measure what is happening very accurately. DateTime.Now doesn't have enough "resolution" to be useful for measuring things as small and fast as what you're using it for. Many times all the time spent inside the code under test will be less than the resolution of DateTime.Now, which will result in the measured time for this test being zero.
For example, when I run the following test on my machine:
#define StopTime
using System;
using System.Diagnostics;
class C
{
static void Main() {
Random rg = new Random();
#if StopTime
Stopwatch stopTime = new Stopwatch();
#else
TimeSpan time = TimeSpan.Zero;
#endif
for(int i=0;i<1000000;++i) {
#if StopTime
stopTime.Start();
#else
DateTime start = DateTime.Now;
#endif
Convert.ToString(rg.Next(0, 99999999)).PadLeft(8, '0');
#if StopTime
stopTime.Stop();
#else
DateTime end = DateTime.Now;
time += end - start;
#endif
}
#if StopTime
Console.WriteLine(stopTime.Elapsed);
#else
Console.WriteLine(time);
#endif
}
}
Measured time using DateTime.Now (00: 00: 00.7680442) approach is about half the time measured with High Resolution Stopwatch (00: 00: 01.6195441)
source to share
None of the things you said, that you tried, should be "slow". Perhaps placing some code will help us find what is wrong. Also, what are the eligibility criteria for slow?
One thing you didn't seem to try is to call Random.Next in such a way as to ensure that the number returned is 8 'chars' long: Random.Next (10000000, 100000000).
source to share
As an aside, I just ran a test on my machine regarding the speed of the approaches mentioned.
On my machine, this gives the following points:
Testing: Guffa1
00:00:05.2472507
Testing: Guffa2
00:00:03.6620228
Testing: Simple
00:00:03.7890637
Testing: Brian
00:00:01.8473002
Testing: JohnDagg
00:00:03.8853139
Testing: chsh
00:00:05.9960557
The only approach that seems to really help is to skip the StringBuilder and work directly from the character buffer
The code follows:
using System;
using System.Text;
using System.Diagnostics;
class C
{
const int IterationCount = 10000000;
static void Main() {
Test("Guffa1", Guffa1);
Test("Guffa2", Guffa2);
Test("Simple", Simple);
Test("Brian", Brian);
Test("JohnDagg", JohnDagg);
Test("chsh", chsh);
}
delegate string TestDelegate(Random rg);
private static void Test(string name, TestDelegate testMethod) {
Console.WriteLine("Testing: " + name);
Random rg = new Random(0);//Start each test with the same random seed
//Call the method once outside of the test to make sure the JIT has run etc.
for(int i=0;i<1000000;++i) {
testMethod(rg);
}
Stopwatch timer = new Stopwatch();
timer.Start();
for(int i=0;i<IterationCount;++i) {
testMethod(rg);
}
timer.Stop();
Console.WriteLine(timer.Elapsed);
}
private static string Simple(Random rg) {
return Convert.ToString(rg.Next(0, 99999999)).PadLeft(8, '0');
}
private static string Brian(Random rg) {
char[] fauxbuilder = new char[8];
int num = rg.Next(0, 100000000);
for (int i = 0; i < 8; i++) {
fauxbuilder[i] = (char)((num % 10) + 48);
num /= 10;
}
return new string(fauxbuilder);
}
private static string Guffa1(Random rg) {
StringBuilder builder = new StringBuilder(8);
for (int i = 0; i < 8; i++) {
builder.Append((char)rg.Next(48,58));
}
return builder.ToString();
}
private static string Guffa2(Random rg) {
StringBuilder builder = new StringBuilder(8);
int num = rg.Next(0, 100000000);
for (int i = 0; i < 8; i++) {
builder.Append((char)((num % 10) + 48));
num /= 10;
}
return builder.ToString();
}
private static string JohnDagg(Random rg) {
StringBuilder builder = new StringBuilder(8);
byte[] b = new byte[8];
rg.NextBytes(b);
for (int i = 0; i < 8; i++) {
builder.Append((char)((b[i] % 10) + 48));
}
return builder.ToString();
}
private static string chsh(Random rg) {
return (
NextSpecial(rg, 10000000) +
NextSpecial(rg, 1000000) +
NextSpecial(rg, 100000) +
NextSpecial(rg, 10000) +
NextSpecial(rg, 1000) +
NextSpecial(rg, 100) +
NextSpecial(rg, 10) +
NextSpecial(rg, 1))
.ToString().PadLeft(8,'0');
}
static int NextSpecial(Random rg, int multiplier) {
return rg.Next(0, 10) * multiplier;
}
}
source to share
This should be quite effective. It allocates an eight character buffer and appends characters to it. There is no extra step of converting each digit to a string and string concatenation, the characters are placed directly into the buffer. The buffer is then returned as a string, so there is no extra step of creating a string from the buffer:
StringBuilder builder = new StringBuilder(8);
for (int i = 0; i < 8; i++) {
builder.Append((char)rnd.Next(48,58));
}
string code = builder.ToString();
Including one random number in the string has the advantage of only calling the random generator once. You can make it faster by doing the string conversion yourself:
StringBuilder builder = new StringBuilder(8);
int num = rnd.Next(0, 100000000);
for (int i = 0; i < 8; i++) {
builder.Append((char)((num % 10) + 48));
num /= 10;
}
string code = builder.ToString();
(Note that the second parameter for the Next method is not inclusive, so it must be 100000000 and not 99999999. This number is actually mapped back to the string in this way, but it doesn't matter since it's a random number.)
source to share
You are probably describing in your script that string operations take the same or longer time as calls to Random.Next (). I haven't tested libraries, but converting a binary random number to decimal string is likely to be much slower than generating it. When you generate a string character-by-character, this is most likely the case.
So consider storing the number as an int if possible and then converting it just for display purposes.
source to share
Well, I decided to try and beat Guff :) I suspected that his version touched too much. So, here is a variant of his solution, which uses a character array instead of a character array. It works about 70% of the time its faster solution when I compared it with Stopwatch
.
char[] fauxbuilder = new char[8];
int num = rnd.Next(0, 100000000);
for (int i = 0; i < 8; i++)
{
fauxbuilder[i] = (char)((num % 10) + 48);
num /= 10;
}
string code = new string(fauxbuilder);
source to share
Similar to Goof's answer, but arguably faster at the nanoscale, as it avoids "costly" separation.
Random rnd = new Random();
StringBuilder builder = new StringBuilder(8);
byte[] b = new byte[8];
rnd.NextBytes(b);
for (int i = 0; i < 8; i++)
{
builder.Append((char)((b[i] % 10) + 48));
}
string code = builder.ToString();
This code is what I hacked together as a solution and has no real optimization (other than initializing the list to a known size). On my system, it was less than 1 second for repeated tests. I have an entry at the end of the file for quality control purposes. It seems to generate what you are looking for, but feel free to point out any flaws.
static Random rand = new Random();
static int NextSpecial(this Random r, int multiplier)
{
return r.Next(0, 10) * multiplier;
}
static string randomString()
{
return (rand.NextSpecial(10000000) +
rand.NextSpecial(1000000) +
rand.NextSpecial(100000) +
rand.NextSpecial(10000) +
rand.NextSpecial(1000) +
rand.NextSpecial(100) +
rand.NextSpecial(10) +
rand.NextSpecial(1))
.ToString().PadLeft(8,'0');
}
static void Main()
{
int MAXITEMS = 1000000;
IList<string> numbers = new List<string>(MAXITEMS);
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < MAXITEMS; i++)
{
numbers.Add(randomString());
}
sw.Stop();
Console.WriteLine("{0} iterations took: {1}", MAXITEMS.ToString(), sw.Elapsed);
File.WriteAllLines(@"c:\test.txt", numbers.ToArray());
Console.ReadLine();
}
source to share