在 C# 中实现 monte carlo 模拟时的奇怪行为
Strange behavior when implementing monte carlo simulation in C#
我正在尝试使用 带路径压缩的加权快速联合 方法在 C# windows 表单应用程序中实施 Monte Carlo 模拟。首先,我采用 gridSize
然后生成一个等于 gridSize * gridSize
的网格,由代表 站点 的黑色背景标签组成,然后我 随机 union 一些站点并将它们的背景设置为白色。问题是每当我 运行 项目时,我得到的整个站点都是黑色的 -无论我尝试多少次-,但是如果我添加断点并逐步进行, 我按预期得到了一些白色网站!!!
代码如下:
public partial class Form1 : Form
{
int gridSize = 0;
byte siteSize = 50;
FlowLayoutPanel flowLayoutPanel;
UnionFind uf;
int randomFirstNumber;
int randomSecondNumber;
string labelName = string.Empty;
Random rnd = new Random();
int numberOfAttempts;
Label label;
HashSet<int> excludeHashSet = new HashSet<int>();
public Form1()
{
InitializeComponent();
}
private void setGridSizeButton_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(gridSizeTextbox.Text) && int.TryParse(gridSizeTextbox.Text, out gridSize))
{
if (gridSize > 0)
{
var flowLayoutPanel = createFlowLayoutPanelAndLabels();
setGridSizeButton.Enabled = false;
gridSizeTextbox.Enabled = false;
randomlyUnionSitesAndWhiteThem();
}
}
}
private void reset_Click(object sender, EventArgs e)
{
setGridSizeButton.Enabled = true;
gridSizeTextbox.Enabled = true;
gridSizeTextbox.Text = string.Empty;
flowLayoutPanel.Dispose();
excludeHashSet = new HashSet<int>();
gridSizeTextbox.Focus();
}
private FlowLayoutPanel createFlowLayoutPanelAndLabels()
{
flowLayoutPanel = new FlowLayoutPanel();
flowLayoutPanel.Name = "flowLayoutPanel";
flowLayoutPanel.Location = new Point(30, 60);
flowLayoutPanel.Size = new Size(gridSize, gridSize);
flowLayoutPanel.BorderStyle = BorderStyle.Fixed3D;
flowLayoutPanel.Width = siteSize * gridSize + 10 * gridSize;
flowLayoutPanel.Height = siteSize * gridSize + 10 * gridSize;
for (int i = 0; i < gridSize * gridSize; i++)
{
var label = new Label();
label.Text = "";
label.Name = i.ToString();
label.Width = siteSize;
label.Height = siteSize;
label.BackColor = Color.Black;
label.Margin = new Padding(3);
label.BorderStyle = BorderStyle.FixedSingle;
flowLayoutPanel.Controls.Add(label);
}
this.Controls.Add(flowLayoutPanel);
return flowLayoutPanel;
}
private void randomlyUnionSitesAndWhiteThem()
{
uf = new UnionFind(gridSize * gridSize);
numberOfAttempts = rnd.Next(0, gridSize * gridSize);
for (int i = 0; i < numberOfAttempts; i++)
{
randomFirstNumber = getRandomNumber(0, gridSize * gridSize, excludeHashSet);
randomSecondNumber = getRandomNumber(0, gridSize * gridSize, excludeHashSet);
labelName = uf.union(randomFirstNumber, randomSecondNumber).ToString();
if (Convert.ToInt16(labelName) > -1)
{
label = (Label)flowLayoutPanel.Controls.Find(labelName, false).First();
label.BackColor = Color.White;
excludeHashSet.Add(Convert.ToInt16(labelName));
}
}
}
private int getRandomNumber(int min, int max, HashSet<int> excludes)
{
int randomNumber;
do
{
var range = Enumerable.Range(min, max).Where(i => !excludes.Contains(i));
var rand = new Random();
int index = rand.Next(0, max - excludes.Count);
randomNumber = range.ElementAt(index);
}
while (excludes.Any(x => x == randomNumber));
return randomNumber;
}
}
这是我的 Union
方法:
class UnionFind
{
.....
public int union(int firstNumber, int secondNumber)
{
firstNumber = getRoot(firstNumber);
secondNumber = getRoot(secondNumber);
if (firstNumber == secondNumber)
{
return -1;
}
if (sizes[firstNumber] < sizes[secondNumber])
{
array[firstNumber] = secondNumber;
sizes[secondNumber] += sizes[firstNumber];
return array[firstNumber];
}
else
{
array[secondNumber] = firstNumber;
sizes[firstNumber] += sizes[secondNumber];
return array[secondNumber];
}
}
.....
}
您只需在 randomlyUnionSitesAndWhiteThem() 方法的末尾调用 this.Refresh() 方法 ;)
您在 getRandomNumber 方法中声明了一个新的 Random 对象,该对象始终为您提供相同的值;如果您改用属性 rnd,它就可以工作。
Here is the explanation
我找出了奇怪行为的原因。当在循环中多次使用 Random
class 时会发生这种情况。这可以通过多种方法解决。
例如System.Threading.Thread.Sleep(10);
。在这里,您正在 sleep 循环 n 毫秒。
private int getRandomNumber(int min, int max, HashSet<int> excludes)
{
int randomNumber;
do
{
System.Threading.Thread.Sleep(10);
...
这也可以使用 System.Timers.Timer
来实现,这将产生相同的结果。这种方法也更适合这种问题。
System.Timers.Timer myTimer = new System.Timers.Timer();
private void percolateEverySecond()
{
myTimer.Elapsed += new ElapsedEventHandler(percolateForTimer); //this is the function that will be executed every myTimer.Interval
myTimer.Interval = 1000;
myTimer.Enabled = true;
}
private void percolateForTimer(object source, ElapsedEventArgs e)
{
//randomly white site, then union it with surrounding sites
...
}
private void Start_Click(object sender, EventArgs e)
{
...
percolateEverySecond();
}
有关 System.Threading.Thread.Sleep()
与 System.Timers.Timer
的更多信息,请参见 this。
此外,请注意问题中的上述代码有很大的改进空间,但作为一般规则,您应该让每个函数只负责一个特定任务“Single Responsibility Principle”。
我正在尝试使用 带路径压缩的加权快速联合 方法在 C# windows 表单应用程序中实施 Monte Carlo 模拟。首先,我采用 gridSize
然后生成一个等于 gridSize * gridSize
的网格,由代表 站点 的黑色背景标签组成,然后我 随机 union 一些站点并将它们的背景设置为白色。问题是每当我 运行 项目时,我得到的整个站点都是黑色的 -无论我尝试多少次-,但是如果我添加断点并逐步进行, 我按预期得到了一些白色网站!!!
代码如下:
public partial class Form1 : Form
{
int gridSize = 0;
byte siteSize = 50;
FlowLayoutPanel flowLayoutPanel;
UnionFind uf;
int randomFirstNumber;
int randomSecondNumber;
string labelName = string.Empty;
Random rnd = new Random();
int numberOfAttempts;
Label label;
HashSet<int> excludeHashSet = new HashSet<int>();
public Form1()
{
InitializeComponent();
}
private void setGridSizeButton_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(gridSizeTextbox.Text) && int.TryParse(gridSizeTextbox.Text, out gridSize))
{
if (gridSize > 0)
{
var flowLayoutPanel = createFlowLayoutPanelAndLabels();
setGridSizeButton.Enabled = false;
gridSizeTextbox.Enabled = false;
randomlyUnionSitesAndWhiteThem();
}
}
}
private void reset_Click(object sender, EventArgs e)
{
setGridSizeButton.Enabled = true;
gridSizeTextbox.Enabled = true;
gridSizeTextbox.Text = string.Empty;
flowLayoutPanel.Dispose();
excludeHashSet = new HashSet<int>();
gridSizeTextbox.Focus();
}
private FlowLayoutPanel createFlowLayoutPanelAndLabels()
{
flowLayoutPanel = new FlowLayoutPanel();
flowLayoutPanel.Name = "flowLayoutPanel";
flowLayoutPanel.Location = new Point(30, 60);
flowLayoutPanel.Size = new Size(gridSize, gridSize);
flowLayoutPanel.BorderStyle = BorderStyle.Fixed3D;
flowLayoutPanel.Width = siteSize * gridSize + 10 * gridSize;
flowLayoutPanel.Height = siteSize * gridSize + 10 * gridSize;
for (int i = 0; i < gridSize * gridSize; i++)
{
var label = new Label();
label.Text = "";
label.Name = i.ToString();
label.Width = siteSize;
label.Height = siteSize;
label.BackColor = Color.Black;
label.Margin = new Padding(3);
label.BorderStyle = BorderStyle.FixedSingle;
flowLayoutPanel.Controls.Add(label);
}
this.Controls.Add(flowLayoutPanel);
return flowLayoutPanel;
}
private void randomlyUnionSitesAndWhiteThem()
{
uf = new UnionFind(gridSize * gridSize);
numberOfAttempts = rnd.Next(0, gridSize * gridSize);
for (int i = 0; i < numberOfAttempts; i++)
{
randomFirstNumber = getRandomNumber(0, gridSize * gridSize, excludeHashSet);
randomSecondNumber = getRandomNumber(0, gridSize * gridSize, excludeHashSet);
labelName = uf.union(randomFirstNumber, randomSecondNumber).ToString();
if (Convert.ToInt16(labelName) > -1)
{
label = (Label)flowLayoutPanel.Controls.Find(labelName, false).First();
label.BackColor = Color.White;
excludeHashSet.Add(Convert.ToInt16(labelName));
}
}
}
private int getRandomNumber(int min, int max, HashSet<int> excludes)
{
int randomNumber;
do
{
var range = Enumerable.Range(min, max).Where(i => !excludes.Contains(i));
var rand = new Random();
int index = rand.Next(0, max - excludes.Count);
randomNumber = range.ElementAt(index);
}
while (excludes.Any(x => x == randomNumber));
return randomNumber;
}
}
这是我的 Union
方法:
class UnionFind
{
.....
public int union(int firstNumber, int secondNumber)
{
firstNumber = getRoot(firstNumber);
secondNumber = getRoot(secondNumber);
if (firstNumber == secondNumber)
{
return -1;
}
if (sizes[firstNumber] < sizes[secondNumber])
{
array[firstNumber] = secondNumber;
sizes[secondNumber] += sizes[firstNumber];
return array[firstNumber];
}
else
{
array[secondNumber] = firstNumber;
sizes[firstNumber] += sizes[secondNumber];
return array[secondNumber];
}
}
.....
}
您只需在 randomlyUnionSitesAndWhiteThem() 方法的末尾调用 this.Refresh() 方法 ;)
您在 getRandomNumber 方法中声明了一个新的 Random 对象,该对象始终为您提供相同的值;如果您改用属性 rnd,它就可以工作。
Here is the explanation
我找出了奇怪行为的原因。当在循环中多次使用 Random
class 时会发生这种情况。这可以通过多种方法解决。
例如System.Threading.Thread.Sleep(10);
。在这里,您正在 sleep 循环 n 毫秒。
private int getRandomNumber(int min, int max, HashSet<int> excludes)
{
int randomNumber;
do
{
System.Threading.Thread.Sleep(10);
...
这也可以使用 System.Timers.Timer
来实现,这将产生相同的结果。这种方法也更适合这种问题。
System.Timers.Timer myTimer = new System.Timers.Timer();
private void percolateEverySecond()
{
myTimer.Elapsed += new ElapsedEventHandler(percolateForTimer); //this is the function that will be executed every myTimer.Interval
myTimer.Interval = 1000;
myTimer.Enabled = true;
}
private void percolateForTimer(object source, ElapsedEventArgs e)
{
//randomly white site, then union it with surrounding sites
...
}
private void Start_Click(object sender, EventArgs e)
{
...
percolateEverySecond();
}
有关 System.Threading.Thread.Sleep()
与 System.Timers.Timer
的更多信息,请参见 this。
此外,请注意问题中的上述代码有很大的改进空间,但作为一般规则,您应该让每个函数只负责一个特定任务“Single Responsibility Principle”。