I am new to programming in C# and I'm having problems generating random numbers from input read form a file. I am trying to generate random numbers from the second field on each line from the following input read from a text file
4321,99
5432,79
6543,59
7654,39
The file is read by the following code, then parsed into separate fields where a method is called to generate a random number
private void readFileButton_Click(object sender, EventArgs e)
{
string readString;
inputFile = File.OpenText(sourceFileString);
while (!inputFile.EndOfStream)
{
readString = inputFile.ReadLine();
var flds = readString.Split(',');
string patID = flds[0];
int months = Convert.ToInt32(flds[1]);
Random();
}
inputFile.Close();
}
The method I am using that generates a random number from the second field
private void Random()
{
Random rand2Integer = new Random();
randomInteger = rand2Integer.Next(1, months) + 1;
}
However, this exception is thrown: 'minValue' cannot be greater than maxValue, and I can't wrap my head around it. If I manually enter the data on a form using a text box then the random number is generated as expected. Any input to guide me along?
From your code it looks like you have a class variable months. However, while reading file you have declared a local variable which essentially hides the class variable.
Now when you use Random function, the class variable is used (which must have 0 and causing this error)
replace the following line of code
int months = Convert.ToInt32(flds[1]);
with
months = Convert.ToInt32(flds[1]);
The variable months in Random() is not the same that the one you are assigning in int months = Convert.ToInt32(flds[1]); the last is local to the method.
You should define month outside or pass it as a parameter to Random()
EDIT:
I made minor changes to make code easier to read. Hope it helps.
private void readFileButton_Click(object sender, EventArgs e)
{
// If you use the stream this way it will be disposed automatically.
using (var sr = new StreamReader(sourceFileString))
{
while (!sr.EndOfStream)
{
string readString = sr.ReadLine();
var flds = readString.Split(',');
string patID = flds[0];
int months = int.Parse(flds[1]);
//I prefer parameters more than fields to communicate between methods.
Random(months);
}
}
}
Random randomGenerator = new Random();
private void Random(int months)
{
randomInteger = randomGenerator.Next(1, months) + 1;
}
In the readFile function, you declare a variable called months and initialize it to the number from your file. This appears correct and probably works great.
However, that variable has function scope. You likely have another variable called "months" at the class level (otherwise you would be getting a compile time error). This variable, and not the one set in "readFile", is what is used by the "Random" method. Change the line to:
months = Convert.ToInt32(flds[1]);
and it will work.
As an aside, you should not use a new instance of Random every time you need a draw. It is considered better practice to have one instance per object and reuse it each time you need a new random number.
Related
Sorry if this has been answered before, but I haven't gotten any relevant results by looking up the title, and I'm not sure how else to phrase this.
I've been learning C# recently. Three times now, I've experienced a logic error that was fixed via converting an object to a string.
The first time, I was generating a random number. When I didn't convert it, I generated the same number repeatedly. When I did, the RNG worked as expected. The problem was from initializing the random variable inside the loop, instead of outside.
The second and third times involves me trying to get the first item in a list via indexing. Whenever I don't use .ToString(), the object returns null. When I do convert the item, I get the correct value. What makes it especially weird is what happens when using the debugger. When I place the breakpoint before .ToString(), the value returns null, even if I run the .ToString() line afterwards. When I place the breakpoint at or after .ToString(), the value displays just fine. In addition, I've been using a bound listbox to help me debug, and the bound listbox displays the items in the list perfectly. I still don't know what's causing these problems.
I'd like to know why this is happening. These problems are really annoying, and I'd like to avoid them in the future. If .ToString() is fixing them, then that means there's something that .ToString() does that fixes it, and I'd like to know how & why.
Sorry if any of my phrasing is wonky -- I've never been a good writer.
Update: Sorry, I didn't add the code because I wasn't sure if it was relevant or not. Here are some recreations of the error
For the RNG error:
private int GenerateNumber()
{
Random random = new Random();
int returnInt = random.Next(1, 6);
return returnInt;
}
private void DisplayResult()
{
listBox1.BeginUpdate();
int[] displayArray = new int[5];
foreach (int i in displayArray)
{
int temp = GenerateNumber();
listBox1.Items.Add(temp);
}
listBox1.EndUpdate();
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
DisplayResult();
// displays the same number
}
The .ToString() "fix" is the same code, except for the foreach loop in DisplayResult(), which is changed to:
foreach (int i in displayArray)
{
int temp = GenerateNumber();
MessageBox.Show(temp.ToString());
listBox1.Items.Add(temp);
}
I can't write up the other two right now, but I will ASAP.
Don't make a Random object inside the method. Change:
private int GenerateNumber()
{
Random random = new Random();
int returnInt = random.Next(1, 6);
return returnInt;
}
to:
Random random = new Random();
private int GenerateNumber()
{
int returnInt = random.Next(1, 6);
return returnInt;
}
I am trying to create a global array using data from a file and use that array for calculations in different functions, e.g. a button click handler. When I use my calculation button it says 'Value cannot be null'.
public partial class Form1 : Form
{
double size = 0;
double[] temperture;
public Form1()
{
InitializeComponent();
OpenFileDialog fileDialog = new OpenFileDialog();
if (fileDialog.ShowDialog() == DialogResult.OK)
{
StreamReader sr = new StreamReader(fileDialog.OpenFile());
string line = sr.ReadLine();
double size = Convert.ToDouble(line);
//create array
double[] temperture = new double[(int)size];
for (int i = 0; i < size; i++)
{
line = sr.ReadLine();
//convert line to double and store in the array
temperture[i] = Convert.ToDouble(line);
}
}
}
private void calculateAverageTempertureToolStripMenuItem1_Click(object sender, System.EventArgs e)
{
double sum = temperture.Sum();
double average = ((double)sum) / temperture.Length;
textBox1.Text = "Average Temperature = " + average;
}
}
Don't redeclare a local variable for temperture:
double[] temperture = new double[(int)size];
Re-use the instance variable you already declared as an instance of the class:
temperture = new double[(int)size];
You are almost done!
Try not to put everything into one procedure.
Google for the SOLID principle and remember the S!
In your Form1 class you have fields: temperture and size. Before you display your form, you want to give these fields a value.
Ask operator for a file name
To calculate these values, you need a filename. You decided to ask the operator for the filename. So let's create small procedures in your Form1 class that will do this: one to ask for the filename, one to read the contents of the file and one to put this all together:
private string SelectFileName()
{
using (OpenFileDialog dlg = new OpenFileDialog())
{
// Set properties before showing the dialog, for example:
dlg.Title = "Please select a file";
dlg.CheckFileExists = true;
dlg.InitialDirectory = ...
// etc. Google: OpenFileDialog class
// show the dlg, and if user presses OK, return the filename, otherwise null
var dlgResult = dlg.ShowDialog(this);
if (dlgResult == DialogResult.Ok)
return dlg.FileName;
else
return null;
}
}
OpenFileDialog is Disposable. Remember to always put IDisposable objects inside a using statement, so you can be certain that it is disposed after use, even if an exception is thrown.
For properties see OpenFileDialog and the base class.
Read the file with Temperatures
After the operator selected a file, you can read it. Apparently the first line of the file contains the number of temperatures as a string, every next line contains every temperature in string format.
You decided to use two separate fields: size and temperature. Know that every array has a property length that holds the number of elements in the array, so you won't need field size.
Furthermore, my advice would be to use List<double> instead of double[]. Only use arrays if you know the size of the array before you create it, and if you are certain that you will never have to change the length. With Lists you won't have to care about the number of elements in it, if you add items, the list automatically changes size.
private List<double> Temperatures {get; set;}
(or if you want, make it a field: `private list temperatures;)
private List<double> ReadTemperatureFile(string fileName)
{
// TODO: decide what to do if fileName null, or if file does not exist
// return empty list? or throw ArgumentNullException and FileNotFoundException?
using (var textReader = File.OpenText(fileName))
{
// first line is the expected number of Temperatures in this file
string firstLine = textReader.ReadLine();
if (firstLine == null)
{
// There is no first line
// Todo: return empty array or throw DataNotFoundException?
}
// if here: the first line has been read
int expectedNumberOfTemperatures = Int32.Parse(firstLine);
List<double> temperatures = new List<double>(firstLine);
Actually, to create a List, you don't need to know how Long it will be. Adding the expected size to the constructor is only to improve performance.
If you are free to change the format of the temperature files, consider to remove the line with the number of elements and keep only the temperatures. This way, you can avoid the problem that the first line says that there are 100 temperatures, but the file only contains 50 temperature values.
By the way, only use Int32.Parse(firstLine) if you are absolutely certain that the first line can be parsed to an int. If you want to handle invalid file formats correctly, consider to use:
if (!Int32.TryParse(firstLine, out int expectedNumberOfTemperatures)
{
// TODO: handle invalid file format; return empty array?
// throw InvalidDataException?
}
List<double> temperatures = new List<double>(expectedNumberOfTemperatures);
By the way: the StreamReader that OpenText returns is IDisposable, so I wrapped it in a using statement.
Continuing reading the file:
string temperatureText = textReader.ReadLine();
while (temperatureText != null)
{
// a line has been read, convert to double and add to array
double temperature = Int32.Parse(temperatureText);
temperatures.Add(temperature);
temperaturText = textReader.ReadLine();
}
}
Did you see that I didn't care about the actual number of temperatures in the file? If the first line said that 100 temperatures are expected, but in fact there are 200 temperatures, I just read them all, and add them to the List. The List grows automatically when needed.
Of course, if you only want to read 100 temperatures, even if there are still temperatures left, then use a counter and stop when the expectedNumberOfTemperatures are read.
Only use Int32.Parse if you are certain that the file contains only valid temperatures, otherwise use Int32.TryParse and decide what to do if an invalid line has been read.
By the way, if you use LINQ, your procedure will be much smaller. Most programmers will immediately know what happens:
(assuming that you can change the file such that it only contains temperatures.)
private List<double> ReadTemperatureFile(string fileName)
{
return System.IO.File.ReadAllLines(fileName)
.Select(line => int32.Parse(line))
.ToList();
}
In words: read all lines that are in the text file with fileName. Parse every read line into a double and convert the sequence of parsed doubles to a List.
If you really want to return double[], replace the terminating ToList() with a ToArray()
Or if the first line has to hold the expected number of temperatures and you want to return all temperatures, skip the first line before you convert to doubles:
return System.IO.File.ReadAllLines(fileName)
.Skip(1)
.Select(line => int32.Parse(line))
.ToList();
Calculate Sum and Average of every sequence of doubles
To make this procedure more reusable, I won't make it only for Lists, or for Arrays, but for every sequence of doubles:
private void CalculateAverageTemperature(IEnumerable<double> temperatures)
{
// using LINQ make this a one-liner:
return temperatures.Average();
}
In fact, I wouldn't even bother to create a procedure for this.
Put it all together:
private void FillTemperatures()
{
string fileName = this.SelectFileName();
this.Temperatures = this.ReadTemperatureFile(fileName);
double averageTemperature = this.Temperatures.Average();
this.textBoxAverage.Text = averageTemperature.ToString(); }
}
And finally your event handler:
private void OnMenuItemCalculateAverage(object sender, ...)
{
this.FillTemperatures();
}
I separated the event handler from the actual data processing. If you decide to use a button to select a file and calculate an average, changes will be minimal
Conclusion
Because you separated your code into smaller procedures, that each have only one task, it is much easier for readers to understand what every procedure should do. For you it is much easier to unit test each procedure. If slight changes are needed, for instance you want a button instead of a menu item, or uses type the name of the file in a text box, or you want to support OpenFileDialog as well as a text box with the filename, changes are minimal.
Furthermore: always wrap disposables in a using statement. Use List<...> instead of array, and consider to use LINQ to process sequences of similar items.
I was working on a small program that basically reads from a txt multiple arrays and writes them to another file, but, additionally, it should generate a unique number and place it just before the information. I got the first part working with no problems but the second part is causing me problems even though it should work.
public static void Main(string[] args)
{
StreamReader vehiclereader = new StreamReader(#"C:\Users\Admin\Desktop\Program\vehicles.txt");
string line = vehiclereader.ReadToEnd();
string ID;
string category;
string Type;
string Brand;
string Model;
string Year;
string Colour;
while (line != null)
{
var parts = line.Split(',');
Type = parts[0];
Brand = parts[1];
Model = parts[2];
Year = parts[3];
Colour = parts[4];
Console.WriteLine(line);
string[] lines = { line };
System.IO.File.WriteAllLines(#"C:\Users\Admin\Desktop\Program\vehicles2.txt", lines);
List<string> categories = new List<string>();
categories.Add(Type);
int count = categories.Where(x => x.Equals(Type)).Count();
ID = Type.Substring(0, 4) + count.ToString("00");
Console.ReadKey();
}
}
}
Currently, this code reads from a txt file, displays it into the console and writes it back to another txt file. This is fine, the part that is not willing to work is the unique number generator.
Last 5 lines of code are supposed to add a unique number just before 'Type' data and would always start from 001. If the 'Type' data is identical, then the number just grows in ascending order. Otherwise, the unique number should reset for new types and start counting from 001 and should keep growing for identical types. (e.g. For all lightweight vehicles the counter should be the same and for heavyweight vehicles the counter should be different but count all of the heavy vehicles)
I'm open to any help or suggestions!
There are a variety of issues and suggestions with this code, allow me to list them before providing a corrected version:
StreamReader is disposable, so put it in a "using" block.
ReadToEnd reads the entire file into a single string, whereas your code structure is expecting it to return a line at a time, so you want the "ReadLine" method.
The value of line does not get modified within your loop, so you will get an infinite loop (program that never ends).
(Suggestion) Use lower case letters at the start of your variable names, it will help you spot what things are variables and what are classes/methods.
(Suggestion) The local variables are declared in a wider scope than they are needed. There is no performance hit to only declaring them within the loop, and it makes your program easier to read.
"string[] lines = { line };" The naming implies that you think this will split the single line into an array of lines. But actually, it will just create an array with one item in it (which we've already established is the entire contents of the file).
"category" is an unused variable; but actually, you don't use Brand, Model, Year or Colour either.
It would have helped if the question had a couple of lines as an example of input and output.
Since, you're processing a line at a time, we might as well write the output file a line at a time, rather than hold the entire file in memory at once.
The ID is unused, and that code is after the line writing the output file, so there is no way it will appear in there.
"int count = categories.Where(x => x.Equals(type)).Count();" is inefficient, as it iterates through the list twice: prefer "int count = categories.Count(x => x.Equals(type));"
Removed the "Console.Write", since the output goes to a file.
Is that "Console.ReadKey" meant to be within the loop, or after it? I put it outside.
I created a class to be responsible for the counting, to demonstrate how it is possible to "separate concerns".
Clearly I don't have your files, so I don't know whether this will work.
class Program
{
public static void Main(string[] args)
{
var typeCounter = new TypeCounter();
using (StreamWriter vehicleWriter = new StreamWriter(#"C:\Users\Admin\Desktop\Program\vehicles2.txt"))
using (StreamReader vehicleReader = new StreamReader(#"C:\Users\Admin\Desktop\Program\vehicles.txt"))
{
string line;
while ((line = vehicleReader.ReadLine()) != null)
{
var parts = line.Split(',');
string type = parts[0].Substring(0, 4); // not sure why you're using substring, I'm just matching what you did
var identifier = typeCounter.GetIdentifier(type);
vehicleWriter.WriteLine($"{identifier},{line}");
}
}
Console.ReadKey();
}
}
public class TypeCounter
{
private IDictionary<string, int> _typeCount = new Dictionary<string, int>();
public string GetIdentifier(string type)
{
int number;
if (_typeCount.ContainsKey(type))
{
number = ++_typeCount[type];
}
else
{
number = 1;
_typeCount.Add(type, number);
}
return $"{type}{number:00}"; // feel free to use more zeros
}
}
I seem to of hit a bit of a snag in a program I am trying to make for school. I am suppose to make a new class to receive data and get a total to send to a new screen. Well I did the same thing we did in class, but the difference is that that was from a text box and there is no text box for the data I need to move this time, so the way I tried gave me an error. Here is what I got so far:
public StudentSelection()
{
InitializeComponent();
}
public decimal firstCounter;
public decimal secondCounter;
public decimal finalCounter;
struct StudentScores
{
public string StuId;
public decimal TestOne;
public decimal TestTwo;
public decimal Final;
}
StudentScores[] allStudentScores = new StudentScores[10];
// class level array to hold 10 products - read in from a file
private void StudentSelection_Load(object sender, EventArgs e)
{
// read products file into products structure array
try
{
// ALWAYS initialize a structure array before using the array
// before adding, changing, etc.
for (int x = 0; x < 10; x++)
{
allStudentScores[x].StuId = "";
allStudentScores[x].TestOne = 0;
allStudentScores[x].TestTwo = 0;
allStudentScores[x].Final = 0;
}
int arrayIndex = 0; // needed for incrementing index value of the array
// when reading file into array
// now read file into the array after initialization
StreamReader inputFile;
string lineofdata; // used to hold each line of data read in from the file
string[] ProductStringArray = new string[4]; // 6 element string array to hold
// each "field" read from every line in file
inputFile = File.OpenText("StudentScores.txt"); // open for reading
while (!inputFile.EndOfStream) //keep reading until end of file
{
lineofdata = inputFile.ReadLine(); // ReadLine() reads an entire row of data from file
ProductStringArray = lineofdata.Split(','); //each field is separated by ';'
allStudentScores[arrayIndex].StuId = ProductStringArray[0]; // add first element of array to first column of allProducts
allStudentScores[arrayIndex].TestOne = decimal.Parse(ProductStringArray[1]);
firstCounter += allStudentScores[arrayIndex].TestOne;
allStudentScores[arrayIndex].TestTwo = decimal.Parse(ProductStringArray[2]);
secondCounter += allStudentScores[arrayIndex].TestTwo;
allStudentScores[arrayIndex].Final = decimal.Parse(ProductStringArray[3]);
finalCounter += allStudentScores[arrayIndex].Final;
StudentListView.Items.Add(ProductStringArray[0]);
arrayIndex++; // increment so NEXT row is updated with next read
}
//close the file
inputFile.Close();
}
catch (Exception anError)
{
MessageBox.Show(anError.Message);
}
}
private void NextButton_Click(object sender, EventArgs e)
{
decimal firstResult, secondResult, finalResult, stuOne, stuTwo, stuThree;
string stuName;
// call the method in our datatier class
decimal.TryParse(firstCounter, out firstResult);
decimal.TryParse(secondCounter, out secondResult);
decimal.TryParse(finalCounter, out finalResult);
DataTier.AddOurNumbers(firstResult, secondResult, finalResult);
DataTier.StudentData(stuName, stuOne, stuTwo, stuThree);
// now hide this window and display a third window with the total
this.Hide();
// display third window
ScoreScreen aScoreScreen = new ScoreScreen();
aScoreScreen.Show();
}
}
and my new class
class DataTier
{
// public static variable available to all windows
public static decimal firstTotal, secondTotal, finalTotal, stuTestOne, stuTestTwo, stuTestThree;
// static is not associated with any object
public static string stuIDCode;
// create a public method to access from all windows
public static void AddOurNumbers(decimal NumOne, decimal NumTwo, decimal numThree)
{
// devide to get an average
firstTotal = NumOne / 10;
secondTotal = NumTwo / 10;
finalTotal = numThree / 10;
}
public static void StudentData(string name, decimal testOne, decimal testTwo, decimal testThree)
{
stuIDCode = name;
stuTestOne = testOne;
stuTestTwo = testTwo;
stuTestThree = testThree;
}
}
The error is at the three decimal.TryParse parts and I have no clue why it is not working except the error says "cannot convert from decimal to string". Any help will be appreciated.
Change it from:
decimal.TryParse(firstCounter, out firstResult);
decimal.TryParse(secondCounter, out secondResult);
decimal.TryParse(finalCounter, out finalResult);
DataTier.AddOurNumbers(firstResult, secondResult, finalResult);
To:
DataTier.AddOurNumbers(firstCounter, secondCounter, finalCounter);
The problem is that you're trying to call decimal.TryParse(string s, out decimal result) as decimal.TryParse(decimal s, out decimal result).
Your input is already decimal and doesn't require any conversion.
As a side note is that the code
decimal.TryParse(someString, out someOutputDecimal);
without a proper if statement around it will fail silently (it doesn't inform anything about the failure). In fact the output value is set to 0, and it acts as if no faulty input was received. If the input should always be valid, you should use decimal.Parse(someString) instead. However in some cases defaulting to 0 if the input is invalid, can be the desired behavior.
The decimal.TryParse() method takes a string as its first argument and attempts to convert it to a decimal value. In your case, the variables you are passing to TryParse() are already decimal variables and there is no need to parse them. If you want to just copy class variables to local variables, all you need to do is this:
firstResult = firstCounter;
secondResult = secondCounter;
finalResult = finalCounter;
Or in this particular case, you can just pass the class variables directly into AddOurNumbers:
DataTier.AddOurNumbers(firstCounter, secondCounter, finalCounter);
One thing to note here is that value types, such as decimals and other primitives in C# get copied any time you assign them from one value to another, or pass them into a method. This means that even if the value of firstCounter, secondCounter, or thirdCounter changes after calling DataTier.AddOurNumbers(), the values that your data tier has already recieved will not change.
This question already has answers here:
Closed 12 years ago.
Possible Duplicates:
c# - getting the same random number repeatedly
Random number generator not working the way I had planned (C#)
I have a method that builds a queue of ints:
public Queue<int> generateTrainingInts(int count = 60)
{
Queue<int> retval = new Queue<int>();
for (int i = 0; i < count; i++)
{
retval.Enqueue(JE_Rand.rInt(2001, 100));
}
return retval;
}
JE_Rand.rInt() is just a function that delegates to a function of the Random class:
public static int rInt(int exclUB, int incLB = 0)
{
Random rand = new Random(DateTime.Now.Millisecond);
int t = rand.Next(incLB, exclUB);
rand = null;
return t;
}
But when I call generateTrainingInts, the same number is enqueued each time. However, if I change rInt to use a static instance of the Random class, instead of a local instance (with function scope as it is defined above), then it appears to work correctly (enqueue random integers). Does anybody know why this happens?
Edit:
Dear Answerers who didn't read my question thoroughly,
Like some of you pointed out, I am looking for a good explanation of why this happens. I am not looking for a solution to the same-number-generated problem, because I already fixed that like I said above. Thanks for your enthusiasm though :) I really just want to understand things like this, because my first implementation made more sense conceptually to me.
You need to keep the same Random object. Put it outside your static method as a static member
private static Random rand = new Random();
public static int rInt(int exclUB, int incLB = 0)
{
int t = rand.Next(incLB, exclUB);
return t;
}
Edit
The reason is the finite resolution of the clock used to initialize Random. Subsequent initializations of Random will get the same starting position in the random sequence. When reusing the same Random the next value in the random sequence is always generated.
Try out the following code and I think you'll see why:
void PrintNowAHundredTimes()
{
for (int i = 0; i < 100; ++i)
{
Console.WriteLine(DateTime.Now);
}
}
The Random objects are getting the same seed over and over. This is because the granularity of the system time returned by DateTime.Now is, quite simply, finite. On my machine for example the value only changes every ~15 ms. So consecutive calls within that time period return the same time.
And as I suspect you already know, two Random objects initialized with the same seed value will generate identical random sequences. (That's why it's called pseudorandom, technically.)
You should also be aware that even if it made sense to instantiate a new Random object locally within your method, setting it to null would still serve no purpose (once the method exits there will be no more references to the object anyway, so it will be garbage collected regardless).
public class JE_Rand
{
private static Random rand= new Random(DateTime.Now.Millisecond);
public static int rInt(int exclUB, int incLB = 0)
{
int t = rand.Next(incLB, exclUB);
return t;
}
}