Related
I am doing an exercise from exercism.io, in which I have to generate random names for robots. I am able to get through a bulk of the tests until I hit this test:
[Fact]
public void Robot_names_are_unique()
{
var names = new HashSet<string>();
for (int i = 0; i < 10_000; i++) {
var robot = new Robot();
Assert.True(names.Add(robot.Name));
}
}
After some googling around, I stumbled upon a couple of solutions and found out about the Fisher-Yates algorithm. I tried to implement it into my own solution but unfortunately, I haven't been able to pass the final test, and I'm stumped. If anyone could point me in the right direction with this, I'd greatly appreciate it. My code is below:
EDIT: I forgot to mention that the format of the string has to follow this: #"^[A-Z]{2}\d{3}$"
public class Robot
{
string _name;
Random r = new Random();
string alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string nums = "0123456789";
public Robot()
{
_name = letter() + num();
}
public string Name
{
get { return _name; }
}
private string letter() => GetString(2 ,alpha.ToCharArray(), r);
private string num() => GetString(3, nums.ToCharArray(), r);
public void Reset() => _name = letter() + num();
public string GetString(int length,char[] chars, Random rnd)
{
Shuffle(chars, rnd);
return new string(chars, 0, length);
}
public void Shuffle(char[] _alpha, Random r)
{
for(int i = _alpha.Length - 1; i > 1; i--)
{
int j = r.Next(i);
char temp = _alpha[i];
_alpha[i] = _alpha[j];
_alpha[j] = temp;
}
}
}
The first rule of any ID is:
It does not mater how big it is, how many possible value it has - if you just create enough of them, you will get a colission eventually.
To Quote Trillian from the Hithchikers Guide: "[A colission] is not impossible. Just realy, really unlikely."
However in this case, I think it is you creating Random Instances in a Loop. This is a classical beginners mistake when workign with Random. You should not create a new random isntance for each Robot Instance, you should have one for the application that you re-use. Like all Pseudorandom Number Generators, Random is deterministic. Same inputs - same outputs.
As you did not specify a seed value, it will use the time in milliseconds. Wich is going to the same between the first 20+ loop itterations at last. So it is going to have the same seed and the same inputs, so the same outputs.
The easiest solution for unique names is to use GUIDs. In theory, it is possible to generate non-unique GUIDs but it is pretty close to zero.
Here is the sample code:
var newUniqueName = Guid.NewGuid().ToString();
Sure GUIDs do not look pretty but they are really easy to use.
EDIT: Since the I missed the additional requirement for the format I see that GUID format is not acceptable.
Here is an easy way to do that too. Since format is two letters (26^2 possibile values) and 3 digits (10^3 possible values) the final number of possible values is 26^2 * 10^3 = 676 * 1000 = 676000. This number is quite small so Random can be used to generate the random integer in the range 0-675999 and then that number can be converted to the name. Here is the sample code:
var random = new System.Random();
var value = random.Next(676000);
var name = ((char)('A' + (value % 26))).ToString();
value /= 26;
name += (char)('A' + (value % 26));
value /= 26;
name += (char)('0' + (value % 10));
value /= 10;
name += (char)('0' + (value % 10));
value /= 10;
name += (char)('0' + (value % 10));
The usual disclaimer about possible identical names applies here too since we have 676000 possible variants and 10000 required names.
EDIT2: Tried the code above and generating 10000 names using random numbers produced between 9915 and 9950 unique names. That is no good. I would use a simple static in class member as a counter instead of random number generator.
First, let's review the test you're code is failing against:
10.000 instances created
Must all have distinct names
So somehow, when creating 10000 "random" names, your code produces at least two names that are the same.
Now, let's have a look at the naming scheme you're using:
AB123
The maximum number of unique names we could possibly create is 468000 (26 * 25 * 10 * 9 * 8).
This seems like it should not be a problem, because 10000 < 468000 - but this is where the birthday paradox comes in!
From wikipedia:
In probability theory, the birthday problem or birthday paradox concerns the probability that, in a set of n randomly chosen people, some pair of them will have the same birthday.
Rewritten for the purposes of your problem, we end up asking:
What's the probability that, in a set of 10000 randomly chosen people, some pair of them will have the same name.
The wikipedia article also lists a function for approximating the number of people required to reach a 50% propbability that two people will have the same name:
where m is the total number of possible distinct values. Applying this with m=468000 gives us ~806 - meaning that after creating only 806 randomly named Robots, there's already a 50% chance of two of them having the same name.
By the time you reach Robot #10000, the chances of not having generated two names that are the same is basically 0.
As others have noted, you can solve this by using a Guid as the robot name instead.
If you want to retain the naming convention you might also get around this by implementing an LCG with an appropriate period and use that as a less collision-prone "naming generator".
Here's one way you can do it:
Generate the list of all possible names
For each robot, select a name from the list at random
Remove the selected name from the list so it can't be selected again
With this, you don't even need to shuffle. Something like this (note, I stole Optional Option's method of generating names because it's quite clever and I couldn't be bother thinking of my own):
public class Robot
{
private static List<string> names;
private static Random rnd = new Random();
public string Name { get; private set; }
static Robot()
{
Console.WriteLine("Initializing");
// Generate possible candidates
names = Enumerable.Range(0, 675999).Select(i =>
{
var sb = new StringBuilder(5);
sb.Append((char)('A' + i % 26));
i /= 26;
sb.Append((char)('A' + i % 26));
i /= 26;
sb.Append(i % 10);
i /= 10;
sb.Append(i % 10);
i /= 10;
sb.Append(i % 10);
return sb.ToString();
}).ToList();
}
public Robot()
{
// Note: if this needs to be multithreaded, then you'd need to do some work here
// to avoid two threads trying to take a name at the same time
// Also note: you should probably check that names.Count > 0
// and throw an error if not
var i = rnd.Next(0, names.Count - 1);
Name = names[i];
names.RemoveAt(i);
}
}
Here's a fiddle that generates 20 random names. They can only be unique because they are removed once they are used.
The point about multitheading is very important however. If you needed to be able to generate robots in parallel, then you'd need to add some code (e.g. locking the critical section of code) to ensure that only one name is being picked and removed from the list of candidates at a time or else things will get really bad, really quickly. This is why, when people need a random id with a reasonable expectation that it'll be unique, without worrying that some other thread(s) are trying the same thing at the same time, they use GUIDs. The sheer number of possible GUIDs makes collisions very unlikely. But you don't have that luxury with only 676,000 possible values
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
For testing purposes, I generally have to create a huge array of strings that are all random or unique in code. Is there an easy way to do this in C#...instead of manually instantiating a bunch of strings. If we could use this to also be an array of some custom class with simple properties being initialized, then that would be great.
EDIT: I have tried doing this:
string myItems[] = { "ONE", "TWO" , "THREE" etc..}
For( loop through 100 )
{
Use GetRandomFileName()
}
You can use GUID's
Here goes the code for it.
in c#.NET
string id = System.Guid.NewGuid().ToString();
Console.WriteLine(id);
if you want 100's of those just use a loop.
int i=0;
List<string> id = null;
while(i<100)
{
id.add(System.Guid.NewGuid().ToString());
i++;
}
Hope it helps.
Yes you can but it's largely based on what you specific requirements are.
A random string
Here is an example function that will build you a string of a particular length out of a-z characters by generating the ASCII codes for those characters (97 to 122) and converting them into chars before appending them to our string.
public string BuildRandomString(Random random, int length)
{
var random = new Random();
string text = string.Empty;
for (int i = 0; i < length; i++)
text += (char)random.Next(97, 122);
return text;
}
A list of 100
We can use this to generate a List of 100 random strings like so:
var random = new Random();
var strings = new List<string>();
for (int i = 0; i < 100; i++)
strings.Add(BuildRandomString(random, 10));
Varying length
This can be extended further to get a varying length string like so:
public string BuildRandomString(Random random, int minLength, int maxLength)
{
var text = string.Empty;
var length = random.Next(minLength, maxLength);
for (int i = 0; i < length; i++)
text += (char)random.Next(97, 122);
return text;
}
Further extension
This method can be extended to give whatever you like, an example improvement could be make sure each generated string has at least one vowel so it looks more like a word.
The ASCII table will be handy when extending the method to give more ideal results based on your requirements.
System.Random
You will have noticed the Random object being instantiated and passed in as an argument to the methods. We can't just instantiate a new one in BuildRandomString() because of how 'pseudo-random' numbers work. They take a "seed" which determines what the first number, and second number, etc. is. This has up sides because we can explicitly specify a seed if we want to work on the same 'random' data multiple times by using new Random(102302) for example.
When you don't use a seed the default constructor is used which generates a seed based on the system clock. The problem is that this number will return the same seed when used in close succession so we need to use the same variable. It could easily be a member variable to the class containing our BuildRandomString method also.
From MSDN:
The default seed value is derived from the system clock and has finite resolution. As a result, different Random objects that are created in close succession by a call to the default constructor will have identical default seed values and, therefore, will produce identical sets of random numbers. This problem can be avoided by using a single Random object to generate all random numbers.
Thanks to dialer for pointing it out.
I need to generate a voucher code[ 5 to 10 digit] for one time use only. what is the best way to generate and check if been used?
edited: I would prefer alpha-numeric characters - amazon like gift voucher codes that must be unique.
When generating voucher codes - you should consider whether having a sequence which is predictable is really what you want.
For example, Voucher Codes: ABC101, ABC102, ABC103 etc are fairly predictable. A user could quite easily guess voucher codes.
To protect against this - you need some way of preventing random guesses from working.
Two approaches:
Embed a checksum in your voucher codes.
The last number on a credit card is a checksum (Check digit) - when you add up the other numbers in a certain way, lets you ensure someone has entered a number correctly. See: http://www.beachnet.com/~hstiles/cardtype.html (first link out of google) for how this is done for credit cards.
Have a large key-space, that is only sparsely populated.
For example, if you want to generate 1,000 vouchers - then a key-space of 1,000,000 means you should be able to use random-generation (with duplicate and sequential checking) to ensure it's difficult to guess another voucher code.
Here's a sample app using the large key-space approach:
static Random random = new Random();
static void Main(string[] args)
{
int vouchersToGenerate = 10;
int lengthOfVoucher = 10;
List<string> generatedVouchers = new List<string>();
char[] keys = "ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890".ToCharArray();
Console.WriteLine("Vouchers: ");
while(generatedVouchers.Count < vouchersToGenerate)
{
var voucher = GenerateVoucher(keys, lengthOfVoucher);
if (!generatedVouchers.Contains(voucher))
{
generatedVouchers.Add(voucher);
Console.WriteLine("\t[#{0}] {1}", generatedVouchers.Count, voucher);
}
}
Console.WriteLine("done");
Console.ReadLine();
}
private static string GenerateVoucher(char[] keys, int lengthOfVoucher)
{
return Enumerable
.Range(1, lengthOfVoucher) // for(i.. )
.Select(k => keys[random.Next(0, keys.Length - 1)]) // generate a new random char
.Aggregate("", (e, c) => e + c); // join into a string
}
Building on the answers from Will Hughes & Shekhar_Pro (and just because I found this question interesting) here's another implementation but I've been a bit liberal with your requirement for the length of the voucher code.
Using a base 32 encoder I found you can use the Tick value to generate alpha-numeric strings. The encoding of a tick count to base 32 produces a 13 character string which can be formatted to make it more readable.
public void GenerateCodes()
{
Random random = new Random();
DateTime timeValue = DateTime.MinValue;
// Create 10 codes just to see the random generation.
for(int i=0; i<10; ++i)
{
int rand = random.Next(3600)+1; // add one to avoid 0 result.
timeValue = timeValue.AddMinutes(rand);
byte[] b = System.BitConverter.GetBytes(timeValue.Ticks);
string voucherCode = Transcoder.Base32Encode(b);
Console.WriteLine(string.Format("{0}-{1}-{2}",
voucherCode.Substring(0,4),
voucherCode.Substring(4,4),
voucherCode.Substring(8,5)));
}
}
Here's the output
AARI-3RCP-AAAAA
ACOM-AAZF-AIAAA
ABIH-LV7W-AIAAA
ADPL-26FL-AMAAA
ABBL-W6LV-AQAAA
ADTP-HFIR-AYAAA
ACDG-JH5K-A4AAA
ADDE-GTST-BEAAA
AAWL-3ZNN-BIAAA
AAGK-4G3Y-BQAAA
If you use a known seed for the Random object and remember how many codes you have already created you can continue to generate codes; e.g. if you need more codes and want to be certain you won't generate duplicates.
Here's one way: Generate a bunch of unique numbers between 10000 and 9999999999 put it in a database. Every time you give one to a user, mark it as used (or delete it if you're trying to save space).
EDIT: Generate the unique alpha-numeric values in the beginning. You'll probably have to keep them around for validation (as others have pointed out).
If your app is limited to using only Numerical digits then i think Timestamps (DateTime.Now.Ticks) can be a good way to get unique code every time. You can use random nums but that will have overhead of checking every number that its been issued already or not. If you can use alphabets also then surely go with GUID.
For checking if its been used or not you need to maintain a database and query it to check for validity.
If you prefer alphanumerical, you could use Guid.NewGuid() method:
Guid g = Guid.NewGuid();
Random rn = new Random();
string gs = g.ToString();
int randomInt = rn.Next(5,10+1);
Console.WriteLine(gs.Substring(gs.Length - randomInt - 1, randomInt));
To check if it was not used store somwhere previously generated codes and compare.
private void AutoPurchaseVouNo1()
{
try
{
int Num = 0;
con.Close();
con.Open();
string incre = "SELECT MAX(VoucherNoint+1) FROM tbl_PurchaseAllCompany";
SqlCommand command = new SqlCommand(incre, con);
if (Convert.IsDBNull(command.ExecuteScalar()))
{
Num = 100;
txtVoucherNoInt1.Text = Convert.ToString(Num);
txtVoucherNo1.Text = Convert.ToString("ABC" + Num);
}
else
{
Num = (int)(command.ExecuteScalar());
txtVoucherNoInt1.Text = Convert.ToString(Num);
txtVoucherNo1.Text = Convert.ToString("ABC" + Num);
}
con.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex, "Error !!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Try this method for creating Voucher Number like ABC100, ABC101, ABC102, etc.
Try this code
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[15];
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
I want to generate 25 unique random numbers and list them in a console. The numbers should be atleast 10 characters long. Any easy way to do that?
Try building the numbers up as strings, and use a HashSet to ensure they are unique:
Random random = new Random();
HashSet<string> ids = new HashSet<string>();
while (ids.Count < 25)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; ++i)
{
sb.Append(random.Next(10));
}
ids.Add(sb.ToString());
}
Example output:
7895499338
2643703497
0126762624
8623017810
...etc...
The class HashSet is present in .NET 3.5 and newer.
The problem lies a little in "25 unique random". Displaying 25 random numbers is as easy as
Random r = new Random();
for(int i=0; i<25; i++)
Console.WriteLine(r.Next(1,100).ToString());
These are not necessarily unique, though. If you do not want to allow duplicates, you need to store previously generated numbers somehow, and roll again if you hit an old one.
Be aware that you change the probability distribution of your generated numbers this way.
Edit: I've just noticed that these numbers should be ten characters long. Since 9,999,999,999 exceeds Int32.MaxValue, I'd suggest using Math.Floor(r.NextDouble() * 10000000000 + 1000000000) instead of r.Next(1,100).
Since your numbers are that long, you should not need to worry about duplicates. They are very very unlikely.
There is a big different between Randomness and Uniqueness.
So if you need really unique numbers you have to make sure that you save somewhere all already created numbers and check if your newly created one isn't within this list or you have to provide some algorithm that ensures that a given number can't created twice.
To get the second part to work you mostly take the date/time of the creation moment, cause the current date/time pair is unique forever. The only problem is how many creations per (milli)second do you have and how many digits are available to store your unique number.
A sample about using 12 digits is made here. Hope this helps.
One simple way is this:
class Test
{
private static void Main()
{
Random rand = new Random();
for (int i = 0; i < 25; ++i)
{
Console.WriteLine(rand.Next(1000000000, int.MaxValue));
}
}
}
This will ensure that the numbers are always 10 characters (digits) long. They will not necessarily be unique however. If you want them to definitely be unique, you'll have to do something like this:
class Test
{
private static void Main()
{
Random rand = new Random();
var generatedSoFar = new HashSet<int>();
for (int i = 0; i < 25; ++i)
{
int newRand;
do
{
newRand = rand.Next(1000000000, int.MaxValue);
} while (generatedSoFar.Contains(newRand)); // generate a new random number until we get to one we haven't generated before
generatedSoFar.Add(newRand);
Console.WriteLine(newRand);
}
}
}
If you want to be able to have more than ten digits, you generate the number of digits randomly between 10 and your max number of digits. Then generate each digit (or group of digits) randomly in a StringBuilder or List. You can use the same HashSet method I used above to ensure uniqueness.
Random rnd = new Random(table);
for(int i = 0; i < 25; ++i) {
Console.WriteLine("{0}", rnd.Next(50, 50+i)
}
I use this code to generate a random number.
Random R = new Random(0);
int Rand = R.Next(7);
but i get the same random number in each run of program.
Remove the 0 from the constructor and you'll get different random numbers.
If you pass a number to the constructor it's used as seed, by always specifying 0 you'll always get the same sequence.
You can specify an int32 which is random, but the easiest is to just not pass any parameter and you get a timebased seed
you have to change the seed of your random number generator object everytime you run your program, as what i've seen from you example, your current seed is 0, so you have to change it to something else if you want to get a different stream of random number... just a thought!
Seed your (pseudo)-random generator using a non-constant value, e.g. current time and date:
Random R = new Random(DateTime.Now.Ticks);
Read more about pseudo-random generators at Wikipedia.
Use time as initial seed of your PRNG.
You need to seed the random generator. You can use as follows:
Random R = new Random(DateTime.Now.Millisecond);
int Rand = R.Next(7);
Random number generators generate a new 'random' value based on the previous number generated. The seed is the initial value for this.
Seeding with the same value (like 0 in your example code) basically tells the random number generator to start with the same number each time. Having the exact same random number generated each time means your code becomes restartable. Example: Simulations use this to restart the simulation with changed parameters, but with the same 'data set'.
Another example:
I want to send myself a motivational message each day. Sometimes the messages are garbled. Being able to rerun the script, producing the same message again and again during a day, makes fixing this simple. In Perl code this means:
# By initialising the random generator with the day number since
# the epoch, we get the same quote during one day.
srand(time()/(24*3600));
my $i = int(rand(#messages));
If you want to produce different numbers each time, you will have to set this seed to something random. The options are many, like time, PID, delay between two keystrokes by the user, some value derived from the ethernet interface, etc. or more likely a combination of the above like time*PID.
Hope this clarifies the idea behind the concept of a random number seed value.
if we want a random number between 1 and 100 the code would look like this:
RandomNumberGenerator.GetRandomInt(1, 100)
The most secure way to generate random number is to use the System.Security.Cryptography.RandomNumberGenerator class.
Here is an example that will generate a number between 1 and 100;
public Number Rand()
{
byte[] Salt = new byte[8];
System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(Salt);
decimal result = 0;
foreach (byte b in Salt)
{
result = result * 255 + b;
}
while (result > 100)
{
result /= 10;
}
return result
}
Complete Code:
public static class RandomHelper
{
static object _myLock = new object();
static Random _random = new Random();
public static int RandomNumber(int min, int max)
{
lock (_myLock)
{
if (min == max)
return min;
if (min > max)
return _random.Next(max, min);
return _random.Next(min, max);
}
}
You need to seed the Random class with something more variable than 0. I normally use DataTime.Now.Ticks or you could use a new Guid's integer value.