I am trying to simulate the Monty Hall Problem (because I read from the book Think Statistics that a guy in particular was convinced only after seeing a computer simulation) in C#, the programming language most familiar to me. My scenario is such that the position of the prize is random (in each run), my choice is random, and the game host's choice of opening the door is random (it can't be random if I picked the non-prize).
Surprisingly though, my program achieves the result of a 50:50 chance of winning no matter whether I switch or not. Here's the code to it (pardon me for the lengthiness):
class Program
{
static void Main(string[] args)
{
Random rand = new Random();
int noSwitchWins = RunGames(rand, false, 10000);
int switchWins = RunGames(rand, true, 10000);
Console.WriteLine(string.Format("If you don't switch, you will win {0} out of 1000 games.", noSwitchWins));
Console.WriteLine(string.Format("If you switch, you will win {0} out of 1000 games.", switchWins));
Console.ReadLine();
}
static int RunGames(Random rand, bool doSwitch, int numberOfRuns)
{
int counter = 0;
for (int i = 0; i < numberOfRuns; i++)
{
bool isWin = RunGame(rand, doSwitch);
if (isWin)
counter++;
}
return counter;
}
static bool RunGame(Random rand, bool doSwitch)
{
int prize = rand.Next(0, 2);
int selection = rand.Next(0, 2);
// available choices
List<Choice> choices = new List<Choice> { new Choice(), new Choice(), new Choice() };
choices[prize].IsPrize = true;
choices[selection].IsSelected = true;
Choice selectedChoice = choices[selection];
int randomlyDisplayedDoor = rand.Next(0, 1);
// one of the choices are displayed
var choicesToDisplay = choices.Where(x => !x.IsSelected && !x.IsPrize);
var displayedChoice = choicesToDisplay.ElementAt(choicesToDisplay.Count() == 1 ? 0 : randomlyDisplayedDoor);
choices.Remove(displayedChoice);
// would you like to switch?
if (doSwitch)
{
Choice initialChoice = choices.Where(x => x.IsSelected).FirstOrDefault();
selectedChoice = choices.Where(x => !x.IsSelected).FirstOrDefault();
selectedChoice.IsSelected = true;
}
return selectedChoice.IsPrize;
}
}
class Choice
{
public bool IsPrize = false;
public bool IsSelected = false;
}
This is entirely for my own interest's sake, and I wrote it in the way most familiar and comfortable to me. Do feel free to offer your own opinion and critique, thank you very much!
rand.Next(0,2)
only returns 0 or 1; the upper bound is exclusive. You are never picking the third door (unless you switch), and the third door never has the prize. You are modelling the wrong problem.
Try instead:
rand.Next(0,3)
Likewise:
int randomlyDisplayedDoor = rand.Next(0, 1);
only ever selects the first of the candidate doors; should be:
int randomlyDisplayedDoor = rand.Next(0, 2);
Now we get:
If you don't switch, you will win 3320 out of 1000 games.
If you switch, you will win 6639 out of 1000 games.
Note - the upper bound is inclusive when equals - i.e. rand.Next(1,1) always returns 1.
See Random.Next(minValue, maxValue)
Parameters
minValue
Type: System.Int32
The inclusive lower bound of the random number returned.
maxValue
Type: System.Int32
The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.
To add to Marc's answer, you can also use Random.Next(Int32) since your lower bound is 0, so it would be simply:
rand.Next(3)
Related
I have 2 arrays one of the types of numbers that will be used and the 2nd array is how many times that number can be used. I have a letter that determines what kind of method will be used I need to figure out how many times I can use a certain number from an array to determine a letter+number The ‘number’ is what I have to make with all the available numbers I can use. If the number cannot be made I would like to just say number cant be made or anything but allow the program to move on.
Here is what I have
int[] picksToUse = { 100, 50, 20, 10, 5, 1 };
int[] timesToUse = { 10, 10, 10, 10, 10, 10 };
string choice = Console.ReadLine();
string input = "";
if(choice.Length > 2)
{
input = choice.Substring(choice.IndexOf("$") + 1);
}
if(...){
}
else if (choice.Equals("D"))
{
int amt = Convert.ToInt32(input);
// code here to determine if number can be made with above choices
Dispense(amt, timesToUse);
}
Assuming picksToUse and timesToUse are exactly the same as you declared them, here's a way to know if you have enough of everything in stock to "pay up". It's a boolean function, which uses recursion. You would call it with the amount needed as parameter, and it would tell you right there if you have enough of everything.
Private Function HasCashInStock(amount As Integer, Optional index As Integer = 0) As Boolean
Dim billsNeeded As Integer = amount \ picksToUse(index)
If billsNeeded > timesToUse(index) Then
Return False
End If
amount -= picksToUse(index) * billsNeeded
If amount = 0 Then
Return True
End If
Return HasCashInStock(amount, index + 1)
End Function
The \ is an integer division operator (in VB.NET, at least - I'm shamelessly letting you translate this code). If you're not familiar with the integer division operator, well, when you use it with integer it gets rid of the floating numbers.
3 / 2 is not valid on integers, because it would yield 1.5.
3 \ 2 is valid on integers, and will yield 1.
That's all there is to it, really. Oh yeah, and recursion. I like recursion, but others will tell you to avoid it as much as you can. What can I say, I think that a nice recursive function has elegance.
You can also totally copy this function another time, modify it and use it to subtracts from your timesToUse() array once you know for sure that there's enough of everything to pay up.
If HasCashInStock(HereIsTheAmountAsInteger) Then
GivesTheMoney(HereIsTheAmountAsInteger)
End If
Having two functions is not the leanest code but it would be more readable. Have fun!
This is what I used to finish my project.
public static bool Validate(int amount, int[] total, int[] needed)
{
int[] billCount = total;
int[] cash = { 100, 50, 20, 10, 5, 1 };
int total = amount;
bool isValid = true;
for (int i = 0; i < total.Length; i++)
{
if(total >= cash[i])
{
billCount[i] = billCount[i] - needed[i];
}
if(billCount[i] < 0)
{
isValid = false;
break;
}
}
return isValid;
}
I got some working code. I did it with a class. I remember when I couldn't see what good classes were. Now I can't brush my teeth without a class. :-)
I force myself to do these problems to gain a little experience in C#. Now I have 3 things I like about C#.
class ATM
{
public int Denomination { get; set; }
public int Inventory { get; set; }
public ATM(int denom, int inven)
{
Denomination = denom;
Inventory = inven;
}
}
List<int> Bills = new List<int>();
List<ATM> ATMs = new List<ATM>();
private void OP2()
{
int[] picksToUse = { 100, 50, 20, 10, 5, 1 };
foreach (int d in picksToUse )
{
ATM atm = new ATM(d, 10);
ATMs.Add(atm);
}
//string sAmtRequested = Console.ReadLine();
string sAmtRequested = textBox1.Text;
if (int.TryParse(sAmtRequested, out int AmtRequested))
{
int RunningBalance = AmtRequested;
do
{
ATM BillReturn = GetBill(RunningBalance);
if (BillReturn is null)
{
MessageBox.Show("Cannot complete transaction");
return;
}
RunningBalance -= BillReturn.Denomination;
} while (RunningBalance > 0);
}
else
{
MessageBox.Show("Non-numeric request.");
return;
}
foreach (int bill in Bills)
Debug.Print(bill.ToString());
Debug.Print("Remaining Inventory");
foreach (ATM atm in ATMs)
Debug.Print($"For Denomination {atm.Denomination} there are {atm.Inventory} bills remaining");
}
private ATM GetBill(int RequestBalance)
{
var FilteredATMs = from atm in ATMs
where atm.Inventory > 0
orderby atm.Denomination descending
select atm;
foreach (ATM bill in FilteredATMs)
{
if (RequestBalance >= bill.Denomination )
{
bill.Inventory -= 1;
Bills.Add(bill.Denomination);
return bill;
}
}
return null;
}
This is my random unique numbers generator I try to create for my cards software. It generates numbers and write into array OK. I have problem with the loop here. when integer i reaches 29, it stops growing and code cycles infinitely and never reaches 30, which would stop the loop.
Without the if statement it works, but it won't fill the range needed.
fixed the code, now works OK, the initial value in array was the problem. now I ged needed 0-29 values
public partial class Form1 : Form
{
int[] rndCards = new int[30];
public Form1()
{
InitializeComponent();
richTextBox1.Text = #"random numbers";
}
private void button1_Click(object sender, EventArgs e)
{
int i = 0;
rndCards = new int[30];
richTextBox1.Clear();
Random rnd = new Random();
while (i < 30)
{
int cardTest = rnd.Next(0, 30);
while (rndCards.Contains(cardTest))
{
cardTest++;
if (cardTest == 31)
{
cardTest = 1;
}
}
rndCards[i] = cardTest;
i++;
}
i = 0;
while (i < 30)
{
rndCards[i] = rndCards[i] -1;
richTextBox1.Text += rndCards[i] + ", ";
i++;
}
}
}
You problem lies in the simple fact that the array already contains the number 0 when you create it (because each item of an array is initialized to the default value for its member's type) That's why you should start your i from 1 and not zero.
int i = 1;
Alternative Simpler Approach:
You can do this as a simple random number generation:
Random rnd = new Random();
rndCards = Enumerable.Range(0, 30).OrderBy(x => rnd.Next()).ToArray();
foreach(var card in rndCards)
{
// do something
}
rnd.Next(0,30) would return a random number from 0-29.
From the documentation for Random.Next(Int32, Int32):
The Next(Int32, Int32) overload returns random integers that range from minValue to maxValue – 1. However, if maxValue equals minValue, the method returns minValue.
Use int cardText = rnd.Next(0, 31);, and this should solve your issue.
The upper bound is exclusive (C# Random.Next - never returns the upper bound?).
int cardTest = rnd.Next(0, 31);
I'm working on a roulette wheel class, it should function more or less like a regular roulette wheel where certain numbers can take up a larger portion of the roulette wheel and thus have a higher probability of being chosen.
So far it has passed the more basic unit tests, that is, programatically it works, I can create a roulette wheel and fill it with a bunch of generic values and it will do just that.
However when it comes to my probability testing, I decided to try it out as a 6-sided die, after 10,000,000 trials it should generate an average die roll of about 3,5 unfortunately it doesn't even come close to that the average after 10,000,000 trials is about 2,9 so I am guessing there is a weakness in my number selection somewhere? I have posted unit tests and actual code below:
public class RouletteNumber<T>
{
public readonly T Number;
public readonly int Size;
public RouletteNumber(T number, int size)
{
this.Number = number;
this.Size = size;
}
public static RouletteNumber<T>[] CreateRange(Tuple<T, int>[] entries)
{
var rouletteNumbers = new RouletteNumber<T>[entries.Length];
for (int i = 0; i < entries.Length; i++)
{
rouletteNumbers[i] = new RouletteNumber<T>(entries[i].Item1, entries[i].Item2);
}
return rouletteNumbers;
}
}
public class RouletteWheel<T>
{
private int size;
private RouletteNumber<T>[] numbers;
private Random rng;
public RouletteWheel(params RouletteNumber<T>[] rouletteNumbers)
{
size = rouletteNumbers.Length;
numbers = rouletteNumbers;
rng = new Random();
//Check if the roulette number sizes match the size of the wheel
if (numbers.Sum(n => n.Size) != size)
{
throw new Exception("The roulette number sections are larger or smaller than the size of the wheel!");
}
}
public T Spin()
{
// Keep spinning until we've returned a number
while (true)
{
foreach (var entry in numbers)
{
if (entry.Size > rng.Next(size))
{
return entry.Number;
}
}
}
}
}
[TestMethod]
public void DiceRouletteWheelTest()
{
double expected = 3.50;
var entries = new Tuple<int, int>[]
{
Tuple.Create(1, 1),
Tuple.Create(2, 1),
Tuple.Create(3, 1),
Tuple.Create(4, 1),
Tuple.Create(5, 1),
Tuple.Create(6, 1)
};
var rouletteWheel = new RouletteWheel<int>(RouletteNumber<int>.CreateRange(entries));
var results = new List<int>();
for (int i = 0; i < 10000000; i++)
{
results.Add(rouletteWheel.Spin());
}
double actual = results.Average();
Assert.AreEqual(expected, actual);
}
}
When you call Random.Next(n) it generates a random number between 0 and n-1, not between 0 and n.
Did you account for that?
In fact for a 6-sided die, you would want to call Random.Next(1, 7)
Maybe i'm not understanding it correctly but I'm guessing the problem lies here :
while (true)
{
foreach (var entry in numbers)
{
if (entry.Size > rng.Next(size))
{
return entry.Number;
}
}
}
You're calculating the rng.Next each time you do the if check.
So the first number has a 1 in 6 chance of being taken. Number 2(the next entry in numbers) then has a 2 in 6 chance of being taken(a new number is rendered between 1 and 6). But since you always start from the first you will eventually have more lower numbers.
I also don't see the need for the while(true) in a normal random generator.
I'm guessing this could would work and look like your current code :
var rndValue = rng.Next(size);
foreach (var entry in numbers)
{
if (entry.Size > rndValue)
{
return entry.Number;
}
}
Length = input Long(can be 2550, 2880, 2568, etc)
List<long> = {618, 350, 308, 300, 250, 232, 200, 128}
The program takes a long value, for that particular long value we have to find the possible combination from the above list which when added give me a input result(same value can be used twice). There can be a difference of +/- 30.
Largest numbers have to be used most.
Ex:Length = 868
For this combinations can be
Combination 1 = 618 + 250
Combination 2 = 308 + 232 + 200 +128
Correct Combination would be Combination 1
But there should also be different combinations.
public static void Main(string[] args)
{
//subtotal list
List<int> totals = new List<int>(new int[] { 618, 350, 308, 300, 250, 232, 200, 128 });
// get matches
List<int[]> results = KnapSack.MatchTotal(2682, totals);
// print results
foreach (var result in results)
{
Console.WriteLine(string.Join(",", result));
}
Console.WriteLine("Done.");
}
internal static List<int[]> MatchTotal(int theTotal, List<int> subTotals)
{
List<int[]> results = new List<int[]>();
while (subTotals.Contains(theTotal))
{
results.Add(new int[1] { theTotal });
subTotals.Remove(theTotal);
}
if (subTotals.Count == 0)
return results;
subTotals.Sort();
double mostNegativeNumber = subTotals[0];
if (mostNegativeNumber > 0)
mostNegativeNumber = 0;
if (mostNegativeNumber == 0)
subTotals.RemoveAll(d => d > theTotal);
for (int choose = 0; choose <= subTotals.Count; choose++)
{
IEnumerable<IEnumerable<int>> combos = Combination.Combinations(subTotals.AsEnumerable(), choose);
results.AddRange(from combo in combos where combo.Sum() == theTotal select combo.ToArray());
}
return results;
}
public static class Combination
{
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int choose)
{
return choose == 0 ?
new[] { new T[0] } :
elements.SelectMany((element, i) =>
elements.Skip(i + 1).Combinations(choose - 1).Select(combo => (new[] { element }).Concat(combo)));
}
}
I Have used the above code, can it be more simplified, Again here also i get unique values. A value can be used any number of times. But the largest number has to be given the most priority.
I have a validation to check whether the total of the sum is greater than the input value. The logic fails even there..
The algorithm you have shown assumes that the list is sorted in ascending order. If not, then you shall first have to sort the list in O(nlogn) time and then execute the algorithm.
Also, it assumes that you are only considering combinations of pairs and you exit on the first match.
If you want to find all combinations, then instead of "break", just output the combination and increment startIndex or decrement endIndex.
Moreover, you should check for ranges (targetSum - 30 to targetSum + 30) rather than just the exact value because the problem says that a margin of error is allowed.
This is the best solution according to me because its complexity is O(nlogn + n) including the sorting.
V4 - Recursive Method, using Stack structure instead of stack frames on thread
It works (tested in VS), but there could be some bugs remaining.
static int Threshold = 30;
private static Stack<long> RecursiveMethod(long target)
{
Stack<long> Combination = new Stack<long>(establishedValues.Count); //Can grow bigger, as big as (target / min(establishedValues)) values
Stack<int> Index = new Stack<int>(establishedValues.Count); //Can grow bigger
int lowerBound = 0;
int dimensionIndex = lowerBound;
long fail = -1 * Threshold;
while (true)
{
long thisVal = establishedValues[dimensionIndex];
dimensionIndex++;
long afterApplied = target - thisVal;
if (afterApplied < fail)
lowerBound = dimensionIndex;
else
{
target = afterApplied;
Combination.Push(thisVal);
if (target <= Threshold)
return Combination;
Index.Push(dimensionIndex);
dimensionIndex = lowerBound;
}
if (dimensionIndex >= establishedValues.Count)
{
if (Index.Count == 0)
return null; //No possible combinations
dimensionIndex = Index.Pop();
lowerBound = dimensionIndex;
target += Combination.Pop();
}
}
}
Maybe V3 - Suggestion for Ordered solution trying every combination
Although this isn't chosen as the answer for the related question, I believe this is a good approach - https://stackoverflow.com/a/17258033/887092(, otherwise you could try the chosen answer (although the output for that is only 2 items in set being summed, rather than up to n items)) - it will enumerate every option including multiples of the same value. V2 works but would be slightly less efficient than an ordered solution, as the same failing-attempt will likely be attempted multiple times.
V2 - Random Selection - Will be able to reuse the same number twice
I'm a fan of using random for "intelligence", allowing the computer to brute force the solution. It's also easy to distribute - as there is no state dependence between two threads trying at the same time for example.
static int Threshold = 30;
public static List<long> RandomMethod(long Target)
{
List<long> Combinations = new List<long>();
Random rnd = new Random();
//Assuming establishedValues is sorted
int LowerBound = 0;
long runningSum = Target;
while (true)
{
int newLowerBound = FindLowerBound(LowerBound, runningSum);
if (newLowerBound == -1)
{
//No more beneficial values to work with, reset
runningSum = Target;
Combinations.Clear();
LowerBound = 0;
continue;
}
LowerBound = newLowerBound;
int rIndex = rnd.Next(LowerBound, establishedValues.Count);
long val = establishedValues[rIndex];
runningSum -= val;
Combinations.Add(val);
if (Math.Abs(runningSum) <= 30)
return Combinations;
}
}
static int FindLowerBound(int currentLowerBound, long runningSum)
{
//Adjust lower bound, so we're not randomly trying a number that's too high
for (int i = currentLowerBound; i < establishedValues.Count; i++)
{
//Factor in the threshold, because an end aggregate which exceeds by 20 is better than underperforming by 21.
if ((establishedValues[i] - Threshold) < runningSum)
{
return i;
}
}
return -1;
}
V1 - Ordered selection - Will not be able to reuse the same number twice
Add this very handy extension function (uses a binary algorithm to find all combinations):
//Make sure you put this in a static class inside System namespace
public static IEnumerable<List<T>> EachCombination<T>(this List<T> allValues)
{
var collection = new List<List<T>>();
for (int counter = 0; counter < (1 << allValues.Count); ++counter)
{
List<T> combination = new List<T>();
for (int i = 0; i < allValues.Count; ++i)
{
if ((counter & (1 << i)) == 0)
combination.Add(allValues[i]);
}
if (combination.Count == 0)
continue;
yield return combination;
}
}
Use the function
static List<long> establishedValues = new List<long>() {618, 350, 308, 300, 250, 232, 200, 128, 180, 118, 155};
//Return is a list of the values which sum to equal the target. Null if not found.
List<long> FindFirstCombination(long target)
{
foreach (var combination in establishedValues.EachCombination())
{
//if (combination.Sum() == target)
if (Math.Abs(combination.Sum() - target) <= 30) //Plus or minus tolerance for difference
return combination;
}
return null; //Or you could throw an exception
}
Test the solution
var target = 858;
var result = FindFirstCombination(target);
bool success = (result != null && result.Sum() == target);
//TODO: for loop with random selection of numbers from the establishedValues, Sum and test through FindFirstCombination
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.