In this answer, the below code was posted for creating unique random alphanumeric strings. Could someone clarify for me how exactly they are ensured to be unique in this code and to what extent these are unique? If I rerun this method on different occasions would I still get unique strings?
Or did I just misunderstand the reply and these are not generating unique keys at all, only random?
I already asked this in a comment to that answer but the user seems to be inactive.
public static string GetUniqueKey()
{
int maxSize = 8;
char[] chars = new char[62];
string a;
a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
chars = a.ToCharArray();
int size = maxSize;
byte[] data = new byte[1];
RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
crypto.GetNonZeroBytes(data);
size = maxSize;
data = new byte[size];
crypto.GetNonZeroBytes(data);
StringBuilder result = new StringBuilder(size);
foreach (byte b in data)
{ result.Append(chars[b % (chars.Length - 1)]); }
return result.ToString();
}
There is nothing in the code that guarantees that the result is unique. To get a unique value you either have to keep all previous values so that you can check for duplicates, or use a lot longer codes so that duplicates are practically impossible (e.g. a GUID). The code contains less than 48 bits of information, which is a lot less than the 128 bits of a GUID.
The string is just random, and although a crypto strength random generator is used, that is ruined by how the code is generated from the random data. There are some issues in the code:
A char array is created, that is just thrown away and replaced with another.
A one byte array of random data is created for no apparent reason at all, as it's not used for anything.
The GetNonZeroBytes method is used instead of the GetBytes method, which adds a skew to the distribution of characters as the code does nothing to handle the lack of zero values.
The modulo (%) operator is used to reduce the random number down to the number of characters used, but the random number can't be evenly divided into the number of characters, which also adds a skew to the distribution of characters.
chars.Length - 1 is used instead of chars.Length when the number is reduced, which means that only 61 of the predefined 62 characters can occur in the string.
Although those issues are minor, they are important when you are dealing with crypo strength randomness.
A version of the code that would produce a string without those issues, and give a code with enough information to be considered practically unique:
public static string GetUniqueKey() {
int size = 16;
byte[] data = new byte[size];
RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
crypto.GetBytes(data);
return BitConverter.ToString(data).Replace("-", String.Empty);
}
Uniqueness and randomness are mutually exclusive concepts. If a random number generator is truly random, then it can return the same value. If values are truly unique, although they may not be deterministic, they certainly aren't truly random, because every value generated removes a value from the pool of allowed values. This means that every run affects the outcome of subsequent runs, and at a certain point the pool is exhausted (barring of course the possibility of an infinitely-sized pool of allowed values, but the only way to avoid collisions in such a pool would be the use of a deterministic method of choosing values).
The code you're showing generates values that are very random, but not 100% guaranteed to be unique. After enough runs, there will be a collision.
I need to generate 7 characters of an alphanumeric string. With a small search, I wrote the below code. Performance results are uploaded above
I have used hashtable Class to guarantee uniqueness and also used RNGCryptoServiceProvider Class to get high-quality random chars
results of generating 100.000 - 1.000.000 - 10.000.000 sample
Generating unique strings;
thanks to nipul parikh
public static Tuple<List<string>, List<string>> GenerateUniqueList(int count)
{
uniqueHashTable = new Hashtable();
nonUniqueList = new List<string>();
uniqueList = new List<string>();
for (int i = 0; i < count; i++)
{
isUniqueGenerated = false;
while (!isUniqueGenerated)
{
uniqueStr = GetUniqueKey();
try
{
uniqueHashTable.Add(uniqueStr, "");
isUniqueGenerated = true;
}
catch (Exception ex)
{
nonUniqueList.Add(uniqueStr);
// Non-unique generated
}
}
}
uniqueList = uniqueHashTable.Keys.Cast<string>().ToList();
return new Tuple<List<string>, List<string>>(uniqueList, nonUniqueList);
}
public static string GetUniqueKey()
{
int size = 7;
char[] chars = new char[62];
string a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
chars = a.ToCharArray();
RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
byte[] data = new byte[size];
crypto.GetNonZeroBytes(data);
StringBuilder result = new StringBuilder(size);
foreach (byte b in data)
result.Append(chars[b % (chars.Length - 1)]);
return Convert.ToString(result);
}
Whole Console Application Code below;
class Program
{
static string uniqueStr;
static Stopwatch stopwatch;
static bool isUniqueGenerated;
static Hashtable uniqueHashTable;
static List<string> uniqueList;
static List<string> nonUniqueList;
static Tuple<List<string>, List<string>> generatedTuple;
static void Main(string[] args)
{
int i = 0, y = 0, count = 100000;
while (i < 10 && y < 4)
{
stopwatch = new Stopwatch();
stopwatch.Start();
generatedTuple = GenerateUniqueList(count);
stopwatch.Stop();
Console.WriteLine("Time elapsed: {0} --- {1} Unique --- {2} nonUnique",
stopwatch.Elapsed,
generatedTuple.Item1.Count().ToFormattedInt(),
generatedTuple.Item2.Count().ToFormattedInt());
i++;
if (i == 9)
{
Console.WriteLine(string.Empty);
y++;
count *= 10;
i = 0;
}
}
Console.ReadLine();
}
public static Tuple<List<string>, List<string>> GenerateUniqueList(int count)
{
uniqueHashTable = new Hashtable();
nonUniqueList = new List<string>();
uniqueList = new List<string>();
for (int i = 0; i < count; i++)
{
isUniqueGenerated = false;
while (!isUniqueGenerated)
{
uniqueStr = GetUniqueKey();
try
{
uniqueHashTable.Add(uniqueStr, "");
isUniqueGenerated = true;
}
catch (Exception ex)
{
nonUniqueList.Add(uniqueStr);
// Non-unique generated
}
}
}
uniqueList = uniqueHashTable.Keys.Cast<string>().ToList();
return new Tuple<List<string>, List<string>>(uniqueList, nonUniqueList);
}
public static string GetUniqueKey()
{
int size = 7;
char[] chars = new char[62];
string a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
chars = a.ToCharArray();
RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
byte[] data = new byte[size];
crypto.GetNonZeroBytes(data);
StringBuilder result = new StringBuilder(size);
foreach (byte b in data)
result.Append(chars[b % (chars.Length - 1)]);
return Convert.ToString(result);
}
}
public static class IntExtensions
{
public static string ToFormattedInt(this int value)
{
return string.Format(CultureInfo.InvariantCulture, "{0:0,0}", value);
}
}
Using strictly alphanumeric characters restricts the pool you draw from to 62. Using the complete printable character set(ASCII 32-126) increases your pool to 94, decreasing the likelihood of collision and eliminating creating the pool separately.
Related
I want to generate a one million bit random binary but my problem is that the code take to much time and not execute why that happen?
string result1 = "";
Random rand = new Random();
for (int i = 0; i < 1000000; i++)
{
result1 += ((rand.Next() % 2 == 0) ? "0" : "1");
}
textBox1.Text = result1.ToString();
Concatenating strings is an O(N) operation. Strings are immutable, so when you add to a string the new value is copied into a new string, which requires iterating the previous string. Since you're adding a value for each iteration, the amount that has to be read each time grows with each addition, leading to a performance of O(N^2). Since your N is 1,000,000 this takes a very, very long time, and probably is eating all of the memory you have storing these intermediary throw-away strings.
The normal solution when building a string with an arbitrary number of inputs is to instead use a StringBuilder. Although, a 1,000,000 character bit string is still.. unwieldy. Assuming a bitstring is what you want/need, you can change your code to something like the following and have a much more performant solution.
public string GetGiantBitString() {
var sb = new StringBuilder();
var rand = new Random();
for(var i = 0; i < 1_000_000; i++) {
sb.Append(rand.Next() % 2);
}
return sb.ToString();
}
This works for me, it takes about 0.035 seconds on my box:
private static IEnumerable<Byte> MillionBits()
{
var rand = new RNGCryptoServiceProvider();
//a million bits is 125,000 bytes, so
var bytes = new List<byte>(125000);
for (var i = 0; i < 125; ++i)
{
byte[] tempBytes = new byte[1000];
rand.GetBytes(tempBytes);
bytes.AddRange(tempBytes);
}
return bytes;
}
private static string BytesAsString(IEnumerable<Byte> bytes)
{
var buffer = new StringBuilder();
foreach (var byt in bytes)
{
buffer.Append(Convert.ToString(byt, 2).PadLeft(8, '0'));
}
return buffer.ToString();
}
and then:
var myStopWatch = new Stopwatch();
myStopWatch.Start();
var lotsOfBytes = MillionBits();
var bigString = BytesAsString(lotsOfBytes);
var len = bigString.Length;
var elapsed = myStopWatch.Elapsed;
The len variable was a million, the string looked like it was all 1s and 0s.
If you really want to fill your textbox full of ones and zeros, just set its Text property to bigString.
I have a method which I am using to generate random strings by creating random integers and casting them to char
public static string GenerateRandomString(int minLength, int maxLength)
{
var length = GenerateRandomNumber(minLength, maxLength);
var builder = new StringBuilder(length);
var random = new Random((int)DateTime.Now.Ticks);
for (var i = 0; i < length; i++)
{
builder.Append((char) random.Next(255));
}
return builder.ToString();
}
The problem is that when I call this method frequently, it is creating the same sequence of values, as the docs already says:
The random number generation starts from a seed value. If the same
seed is used repeatedly, the same series of numbers is generated. One
way to produce different sequences is to make the seed value
time-dependent, thereby producing a different series with each new
instance of Random.
As you can see I am making the seed time dependent and also creating a new instance of Random on each call to the method. Even though, my Test is still failing.
[TestMethod]
public void GenerateRandomStringTest()
{
for (var i = 0; i < 100; i++)
{
var string1 = Utilitaries.GenerateRandomString(10, 100);
var string2 = Utilitaries.GenerateRandomString(10, 20);
if (string1.Contains(string2))
throw new InternalTestFailureException("");
}
}
How could I make sure that independently of the frequency on which I call the method, the sequence will "always" be different?
Your test is failing because the GenerateRandomString function completes too soon for the DateTime.Now.Ticks to change. On most systems it is quantized at either 10 or 15 ms, which is more than enough time for a modern CPU to generate a sequence of 100 random characters.
Inserting a small delay in your test should fix the problem:
var string1 = Utilitaries.GenerateRandomString(10, 100);
Thread.Sleep(30);
var string2 = Utilitaries.GenerateRandomString(10, 20);
You're effectively doing the same as Random's default constructor. It's using Environment.TickCount. Take a look at the example in this MSDN documentation for the Random constructor. It shows that inserting a Thread.Sleep between the initialization of the different Random instances, will yield different results.
If you really want to get different values, I suggest you change to a seed value that's not time-dependent.
dasblinkenlight has given why this is happening.
Now you should do this to overcome this problem
public static string GenerateRandomString(Random random , int minLength,
int maxLength)
{
var length = GenerateRandomNumber(random , minLength, maxLength);
var builder = new StringBuilder(length);
for (var i = 0; i < length; i++)
builder.Append((char) random.Next(255));
return builder.ToString();
}
public void GenerateRandomStringTest()
{
Random rnd = New Random();
for (var i = 0; i < 100; i++)
{
var string1 = Utilitaries.GenerateRandomString(rnd, 10, 100);
var string2 = Utilitaries.GenerateRandomString(rnd, 10, 20);
if (string1.Contains(string2))
throw new InternalTestFailureException("");
}
}
My main.cs code:
public string Generate(int length)
{
char[] chars = "$%##!*abcdefghijklmnopqrstuvwxyz1234567890?;:ABCDEFGHIJKLMNOPQRSTUVWXYZ^&".ToCharArray();
string password = string.Empty;
Random random = new Random();
for (int i = 0; i < length; i++)
{
int x = random.Next(1, chars.Length);
if (!password.Contains(chars.GetValue(x).ToString()))
password += chars.GetValue(x);
else
i--;
}
return password;
}
I have make a test code
[TestMethod]
[Timeout(1000)]
public void RenderingPasswordShouldHaveMaximumSize()
{
var amountOfCharacters = Int32.MaxValue;
var generator = new PasswordGenerator();
var target = generator.Generate(amountOfCharacters);
Assert.Fail("This method should throw an exception if you try to create a password with too many characters");
}
But it gives me the following error:
Message: Test 'RenderingPasswordShouldHaveMaximumSize' exceeded execution timeout period
Can someone help me with this? My password needs to have a max size of 74.
I've just noticed you actually want your test to take a long time, ignore the part about Int32.MaxValue.
Your loop is a little strange, you're decrementing the iterator in order to try and utilise all of the letters, but that can cause problems. It's not a case of it taking too long, but of it not really generating a password properly.
I would fix your version like this:
static string Generate(int length)
{
// also, seed your random so you don't get the same password
Random random = new Random((int)DateTime.Now.Ticks);
char[] chars = "$%##!*abcdefghijklmnopqrstuvwxyz1234567890?;:ABCDEFGHIJKLMNOPQRSTUVWXYZ^&".ToCharArray();
string password = string.Empty;
for (int i = 0; i < chars.Length; i++)
{
int x = random.Next(0, chars.Length);
if (!password.Contains(chars.GetValue(x).ToString()))
password += chars.GetValue(x);
else
i--;
}
if (length < password.Length) password = password.Substring(0, length);
return password;
}
I've tested this and it works:
Test code:
Debug.WriteLine(Generate(5));
Thread.Sleep(50);
Debug.WriteLine(Generate(10));
Thread.Sleep(50);
Debug.WriteLine(Generate(20));
Thread.Sleep(50);
Debug.WriteLine(Generate(30));
Thread.Sleep(50);
Debug.WriteLine(Generate(40));
Thread.Sleep(50);
Debug.WriteLine(Generate(60));
Thread.Sleep(50);
And results:
SDxF0
i8ZLhm1gxn
#0Ldn7I&1:Kg2x3SYE;m
U?5uO%N4hkpq1*y;9SRVaer^Eij:bT
nvL;E3#D1MQgTicSdojHOwz:VFk2x&94a*PZ#X80
cSm39n:%1sL*lk2B?yVDTPZCA0vGb#udjeW5KoUw6qRNxY^pazH$JXiQ;tFg
Also, bear in mind that the way you are adding characters to the password, you're making it so that it utilises all characters in the char[] but has no repeats. What this means is that the max length of your password is equal to the length of your char[].
I would recommend something like this:
static string Generate(int length)
{
Random random = new Random((int)DateTime.Now.Ticks);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++)
{
int x = random.Next(33, 123);
sb.Append((char)x);
}
return sb.ToString();
}
Which gives the same results, but can also handle longer lengths, such as 200:
t5[5l
WoEZG;8^9<
6(4Q*Y7k>`?ohte6F^pe
DVb\5l^JMKc`q&[#$U4Kq^\OW`JrRi
#iQSq0\WRoDe<]k36nOhNOb-#"Zt!cK8iaR.I>VF
1<%^"B)6bZhWEiazmfmO7Vv*Rw]TbKV+GRJUc%k23Lq/HC.I6Eha6!5V#v-&
jOnV_`(Cf$I\kFr/%%/loG"bx9lV1E?`[A"y1)pF5tA".x?**(E/>kzCN-XGnL2B`c!JEtxw0cXu'zTztcM*z0CkBYK%LIRcbz<Fxo`0*HzU4&=NTXM,z)CP#1)fJtKa2o3'_/#&#=zR1iW<<wLnwFdYXg&hgta0x:C?m6jEVH*])k#QTgKMIXdg,UJr*59)VkUrG8J&
What about just adding this:
if (length > chars.Length) {
throw new ArgumentException("Password too long", length);
}
On your test, you can check that this Exception is thrown as expected.
Your code will never finish unless you pass a number less than 74.
Acording to your code, if the randomly selected character is already in the password, you just iterage again (decreasing i).
Have you thought what will happen once you have used all the characters of chars?
Generating a random password is easy. but generating a batch is more difficult.
public static string getRandomPassword(int letters, int getallen) {
//int letters = 8;
//int getallen = 5;
char[] letterdeel = new char[letters];
int minGetal = (int)Math.Pow(10, getallen - 1);
int maxGetal = (int)Math.Pow(10, getallen);
string password;
Random r = new Random();
int test = (int)(DateTime.Now.Ticks);
for (int i = 0; i < letters; i++) {
r = new Random((int)(DateTime.Now.Ticks) + i);
bool capital = r.Next(2) == 0 ? true : false;
if (capital) {
letterdeel[i] = (char)r.Next(65, 91);
} else {
letterdeel[i] = (char)r.Next(97, 123);
}
}
password = new string(letterdeel);
password += r.Next(minGetal, maxGetal);
return password;
}
this is my method, the passwords should be in a certain letter-number format.
this works fine, however if i have a for loop pulling 100 passwords from this method, in my array i have 5-8 the same passwords, then again 5-8 the same pass.
i know WHY this is, because of the random function and the clock it depends on, but how do i fix this?
Move Random r to outside the method if you are repeatedly calling it. You are going to be hitting it several times in the same relative timeframe, so you are going to be generating the same seeds. You also want to get rid of the line below. It is unnecessary, and (again), with the nature of DateTime.Now, you would just continue to generate the same sequence of "random" numbers.
r = new Random((int)(DateTime.Now.Ticks) + i);
Define your random number generator as a static outside of the function.
How can I get true randomness in this class without Thread.Sleep(300)?
Use a set rather than whatever collection you store into and don't loop 100 times but until the set has 100 items in it.
I've made a class (code below) that handles the creation of a "matching" quiz item on a test, this is the output:
It works fine.
However, in order to get it completely random, I have to put the thread to sleep for at least 300 counts between the random shuffling of the two columns, anything lower than 300 returns both columns sorted in the same order, as if it is using the same seed for randomness:
LeftDisplayIndexes.Shuffle();
Thread.Sleep(300);
RightDisplayIndexes.Shuffle();
What do I have to do to make the shuffling of the two columns completely random without this time wait?
full code:
using System.Collections.Generic;
using System;
using System.Threading;
namespace TestSort727272
{
class Program
{
static void Main(string[] args)
{
MatchingItems matchingItems = new MatchingItems();
matchingItems.Add("one", "111");
matchingItems.Add("two", "222");
matchingItems.Add("three", "333");
matchingItems.Add("four", "444");
matchingItems.Setup();
matchingItems.DisplayTest();
matchingItems.DisplayAnswers();
Console.ReadLine();
}
}
public class MatchingItems
{
public List<MatchingItem> Collection { get; set; }
public List<int> LeftDisplayIndexes { get; set; }
public List<int> RightDisplayIndexes { get; set; }
private char[] _numbers = { '1', '2', '3', '4', '5', '6', '7', '8' };
private char[] _letters = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' };
public MatchingItems()
{
Collection = new List<MatchingItem>();
LeftDisplayIndexes = new List<int>();
RightDisplayIndexes = new List<int>();
}
public void Add(string leftText, string rightText)
{
MatchingItem matchingItem = new MatchingItem(leftText, rightText);
Collection.Add(matchingItem);
LeftDisplayIndexes.Add(Collection.Count - 1);
RightDisplayIndexes.Add(Collection.Count - 1);
}
public void DisplayTest()
{
Console.WriteLine("");
Console.WriteLine("--TEST:-------------------------");
for (int i = 0; i < Collection.Count; i++)
{
int leftIndex = LeftDisplayIndexes[i];
int rightIndex = RightDisplayIndexes[i];
Console.WriteLine("{0}. {1,-12}{2}. {3}", _numbers[i], Collection[leftIndex].LeftText, _letters[i], Collection[rightIndex].RightText);
}
}
public void DisplayAnswers()
{
Console.WriteLine("");
Console.WriteLine("--ANSWERS:-------------------------");
for (int i = 0; i < Collection.Count; i++)
{
string leftLabel = _numbers[i].ToString();
int leftIndex = LeftDisplayIndexes[i];
int rightIndex = RightDisplayIndexes.IndexOf(leftIndex);
string answerLabel = _letters[rightIndex].ToString();
Console.WriteLine("{0}. {1}", leftLabel, answerLabel);
}
}
public void Setup()
{
do
{
LeftDisplayIndexes.Shuffle();
Thread.Sleep(300);
RightDisplayIndexes.Shuffle();
} while (SomeLinesAreMatched());
}
private bool SomeLinesAreMatched()
{
for (int i = 0; i < LeftDisplayIndexes.Count; i++)
{
int leftIndex = LeftDisplayIndexes[i];
int rightIndex = RightDisplayIndexes[i];
if (leftIndex == rightIndex)
return true;
}
return false;
}
public void DisplayAsAnswer(int numberedIndex)
{
Console.WriteLine("");
Console.WriteLine("--ANSWER TO {0}:-------------------------", _numbers[numberedIndex]);
for (int i = 0; i < Collection.Count; i++)
{
int leftIndex = LeftDisplayIndexes[i];
int rightIndex = RightDisplayIndexes[i];
Console.WriteLine("{0}. {1,-12}{2}. {3}", _numbers[i], Collection[leftIndex].LeftText, _letters[i], Collection[rightIndex].RightText);
}
}
}
public class MatchingItem
{
public string LeftText { get; set; }
public string RightText { get; set; }
public MatchingItem(string leftText, string rightText)
{
LeftText = leftText;
RightText = rightText;
}
}
public static class Helpers
{
public static void Shuffle<T>(this IList<T> list)
{
Random rng = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
}
Move Random rng = new Random(); to a static variable.
MSDN says "The default seed value is derived from the system clock and has finite resolution". When you create many Random objects within a small time range they all get the same seed and the first value will be equal to all Random objects.
By reusing the same Random object you will advance to the next random value from a given seed.
Only make one instance of the Random class. When you call it without a constructor it grabs a random seed from the computer clock, so you could get the same one twice.
public static class Helpers
{
static Random rng = new Random();
public static void Shuffle<T>(this IList<T> list)
{
int n = list.Count;
while (n > 1)
{
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
I have to put the thread to sleep for
at least 300 counts between the random
shuffling of the two columns, anything
lower than 300 returns both columns
sorted in the same order, as if it is
using the same seed for randomness
You've answered your own question here. It is "as if it is using the same seed" because it is using the same seed! Due to the relatively coarse granularity of the Windows system clock, multiple Random instances constructed at nearly the same time will have the same seed value.
As Albin suggests, you should just have one Random object and use that. This way instead of a bunch of pseudorandom sequences that all start at the same seed and are therefore identical, your Shuffle method will be based on a single pseudorandom sequence.
Considering that you have it as an extension method, you may desire for it to be reusable. In this case, consider having an overload that accepts a Random and one that doesn't:
static void Shuffle<T>(this IList<T> list, Random random)
{
// Your code goes here.
}
static void Shuffle<T>(this IList<T> list)
{
list.Shuffle(new Random());
}
This allows the caller to provide a static Random object if he/she's going to be calling Shuffle many times consecutively; on the other hand, if it's just a one-time thing, Shuffle can take care of the Random instantiation itself.
One last thing I want to point out is that since the solution involves using a single shared Random object, you should be aware that the Random class is not thread-safe. If there's a chance you might be calling Shuffle from multiple threads concurrently, you'll need to lock your Next call (or: what I prefer to do is have a [ThreadStatic] Random object for each thread, each one seeded on a random value provided by a "core" Random -- but that's a bit more involved).
Otherwise you could end up with Next suddenly just retuning an endless sequence of zeroes.
The problem is that you are creating your Random objects too close to each other in time. When you do that, their internal pseudo-random generators are seeded with the same system time, and the sequence of numbers they produce will be identical.
The simplest solution is to reuse a single Random object, either by passing it as an argument to your shuffle algorithm or storing it as a member-variable of the class in which the shuffle is implemented.
The way random generators work, roughly, is that they have a seed from which the random values are derived. When you create a new Random object, this seed is set to be the current system time, in seconds or milliseconds.
Let's say when you create the first Random object, the seed is 10000. After calling it three times, the seeds were 20000, 40000, 80000, generating whatever numbers form the seeds (let's say 5, 6, 2). If you create a new Random object very quickly, the same seed will be used, 10000. So, if you call it three times, you'll get the same seeds, 20000, 40000, and 80000, and the same numbers from them.
However, if you re-use the same object, the latest seed was 80000, so instead you'll generate three new seeds, 160000, 320000, and 640000, which are very likely to give you new values.
That's why you have to use one random generator, without creating a new one every time.
Try to use Random() just one time. You'll get the idea.