List populating with repeating data when it shouldn't

Viewed 55

I am working on a programming project for class, and I wanted to add something extra to the project by randomly generating data for it. My issue is that I have a list populating with copies of the same data even though it seems to be generating completely different things each time a new object is created. When I attempt to debug, I encounter very strange behavior. This is my code:

    private void PopulateAtRandom(int amount)
    {
        // create a list of first names from a text file of 200 names
        List<string> firstnames = new List<string>();
        StreamReader reader = new StreamReader("Random First Names.txt");
        while (!reader.EndOfStream)
            firstnames.Add(reader.ReadLine());
        reader.Close();

        // create a list of last names from a text file of 500 names
        List<string> lastnames = new List<string>();
        reader = new StreamReader("Random Last Names.txt");
        while (!reader.EndOfStream)
            lastnames.Add(reader.ReadLine());
        reader.Close();

        // create a list of majors from a text file of 198 majors
        List<string> majors = new List<string>();
        reader = new StreamReader("Majors.txt");
        while (!reader.EndOfStream)
        {
            string line = reader.ReadLine();
            majors.Add(line.Substring(0, line.IndexOf(" - ")));
        }
        reader.Close();

        // create a list of high schools from a text file of 860 schools
        List<string> highschools = new List<string>();
        reader = new StreamReader("All Illinois High Schools.txt");
        while (!reader.EndOfStream)
            highschools.Add(reader.ReadLine().Split(',')[0]);
        reader.Close();

        // create a list of colleges from a text file of 9436 schools
        List<string> colleges = new List<string>();
        reader = new StreamReader("All US Colleges.txt");
        while (!reader.EndOfStream)
            colleges.Add(reader.ReadLine());
        reader.Close();

        students = new List<Student>();
        for (int i = 0; i < amount; i++)
        {
            bool graduate = random.NextDouble() >= 0.5;
            string fName = firstnames[random.Next(firstnames.Count)];
            string lName = lastnames[random.Next(lastnames.Count)];
            string major = majors[random.Next(majors.Count)];
            int gradYear = RandomGauss(1950, 2017, 2013, (graduate ? 10 : 4));
            string prevSchool = graduate ? colleges[random.Next(colleges.Count)] 
                : highschools[random.Next(highschools.Count)];
            string otherInfo = graduate ? RandomWithDefault<string>(major, 0.05, majors)
                : "" + RandomGauss(0, 60, 0, 15) + " transfer credits";

            Student student = new Student(graduate, fName, lName, major, gradYear, prevSchool, otherInfo);
            students.Add(student); /* I put a breakpoint here for debugging */
        }
    }

    /**
     * <summary>
     * Return a random integer in the given range based on the specified gaussian distribution
     * </summary>
     */
    private int RandomGauss(int min, int max, double mean, double sigma){...}

    /**
     * <summary>
     * Randomly return either the default value or a different value based on the given odds
     * </summary>
     */
    private T RandomWithDefault<T>(T defaultValue, double oddsOfOther, List<T> otherOptions){...}

    private void buttonSubmit_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < students.Count; i++)
        {
            Student student = students[i];
            listBox.Items.Add(student); /* I put another breakpoint here for debugging */
        }
    }

I have been using PopulateAtRandom(1000); in my constructor. When buttonSubmit_Click() is called, listBox will display one of two things. The first entry is always unique, then either a) the next 500-ish entries are one student and the rest are a second student, or b) the rest of the entries are alternating between two different students. However, when I go to debug, I can see that every new entry into students is unique, as it should be. Then, when I check how listBox.Items is being populated, I find that same pattern of the first few being unique and the rest displaying only two different students. The actual act of debugging seems to affect this as well. For example, I will stop at the first breakpoint 20 times then let the program finish on its own until I reach the second breakpoint. As I stop on the second breakpoint, I find that each of those 20 students, plus another, display properly, then the following 979 follow that same pattern as earlier. I see this same effect no matter how many times I stop at the first breakpoint.

I have tried searching the internet for similar instances of this behavior, but I am not getting anywhere, which is probably because I am not sure how to word this issue. When I search using the title I provided for this question, I do not get anything remotely related to my problem, so if any of you know of a similar issue, please point me in the right direction. My only thought is that this is an issue with memory allocation. The PopulateAtRandom() method is using up a lot of memory with the lists I create before attempting to populate students, so maybe the program is recycling the same memory address for each new Student, and since students is really just a list of memory addresses, it ends up with repeats of the same addresses. C# does not seem to have a nice way of giving me the memory address of an object, so I haven't been able to confirm that. If this is the case, though, I am still not sure how to circumvent that issue, so any advice would be greatly appreciated. Thank you!

1 Answers

RandomGauss probably leverages the Random class, which creates a seed based on the time when it's instantiated. I'm guessing the RandomGauss method instantiates a new Random instance each time it's invoked. When you aren't debugging, your loop repeats a lot of times before the system's clock ticks to change time, so many of your Random instances end up using the same seed, and hence produce the same result the first time you ask them for a random number.

The solution is to create a single Random instance and storing it to a field on your class.

e.g., instead of this:

/**
 * <summary>
 * Return a random integer in the given range based on the specified gaussian distribution
 * </summary>
 */
private int RandomGauss(int min, int max, double mean, double sigma){
    Random random = new Random();
    // code that uses random ...
}

you want something more like this:

private Random random = new Random();

/**
 * <summary>
 * Return a random integer in the given range based on the specified gaussian distribution
 * </summary>
 */
private int RandomGauss(int min, int max, double mean, double sigma){
    // code that uses random ...
}

PS--there are utility methods that help with reading text from files.

    // create a list of first names from a text file of 200 names
    List<string> firstnames = File.ReadAllLines("Random First Names.txt").ToList();
Related