3

Sorry if this is a stupid noob question. I'm doing a very small project for my girlfriend - a list of countries and she has to enter their capitals (obscure countries, mind you) . Since I'm a total beginner, I had to resort to using two arrays, one for countries and the other for capitals, with matching indexes. That way it's easy to check for the right answer and I don't have to parse any text files or use any data-bases. I'm using random numbers to make it more interesting. To stop the program from generating the same countries over and over again, I'm using a List of integers that keeps tracks of what indexes have already been used and regenerates the number if the list contains the previous one. Pretty basic stuff. Surprisingly, it all works.

But I'm having a problem. How do I check that I've run out of countries, basically? :) I can't simply check the List size against my countries array, since List probably includes more values than the array, and if (taken.Equals(Countries.Length)) doesn't seem to work. Or I can't find the right place in the code to put this check.

Sorry if this is simple, but I can't seem to find a proper solution.

EDIT Wow, what an amazing community. During the short walk from Starbucks to my place I get dozens of quality answers which cover a huge array of design techniques. This is so great! Thank you everyone! Obviously, the question has been answered but I will post the code for you, if anyone has any additional comments.

// JUST A TEST FOR NOW, 13 COUNTRIES

string[] Countries = {"Belgium", "France", "The Netherlands", "Spain", "Monaco", "Belarus", "Germany",
                             "Portugal", "Ukraine", "Russia", "Sweden", "Denmark", "South Africa"};
        string[] Capitals = {"Brussels", "Paris", "Amsterdam", "Madrid", "Monaco", "Minsk", "Berlin",
                            "Lisbon", "Kiev", "Moscow", "Stockholm", "Copenhagen", "Pretoria"};
        Random number = new Random();
        List<int> taken = new List<int>();
        int index;
        int score = 0;

        private int Generate()
        {

            while (true) {
                index = number.Next(0, Countries.Length);
                if (taken.Contains(index)) continue;
                // THIS IS WHAT I WAS INITIALLY TRYING TO DO
                if (taken.Equals(Countries.Length)) { 
                    MessageBox.Show("Game over!");
                    return -1;


                }
                return index;
            }
        }


        private void Form1_Load(object sender, EventArgs e)
        {
            index = Generate();
            taken.Add(index);
            label1.Text = Countries[index];
            label3.Text = "0 out of " + Countries.Length.ToString();

        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (textBox1.Text.Trim() == Capitals[index].ToString()) {
                label2.Text = "You win!";
                index = Generate();
                taken.Add(index);
                label1.Text = Countries[index];
                textBox1.Clear();
                label3.Text = ++score + " out of " + Countries.Length.ToString();

            }
            else {
                label2.Text = "Wrong!";
                textBox1.Clear();
            }
        }
    }
}
Leo
  • 467
  • 1
  • 4
  • 12
  • How can the List contain more values than the array? – Tudor Nov 15 '11 at 18:51
  • So, you simply want to create a randomized permutation of the country / capital pairs to present for answering? – Tejs Nov 15 '11 at 18:52
  • Ok, maybe not, it might have been an exaggeration on my part, but still I can't find the right way to check if their lengths are equal and where to put in my code. At the moment the program just freezes upon me getting the last answer right. – Leo Nov 15 '11 at 18:54
  • No answer here. Just wanted to mention that I wrote this *exact* program for my girlfriend 9 years ago. – dlev Nov 15 '11 at 18:56
  • Just an update that I've added the code. Also have to admit that I used a List for "taken" only because it has an Add() method, and arrays don't... – Leo Nov 15 '11 at 19:35

9 Answers9

5

To stop the program from generating the same countries over and over again, I'm using a List of integers that keeps tracks of what indexes have already been used and regenerates the number if the list contains the previous one.

...

How do I check that I've run out of countries, basically?

You might want to consider an alternative approach, as this is going to be quite expensive and overly complicated.

Instead of trying to add one country at random, checking against ones you've already added, you could just make the entire list of countries, then perform a shuffle ("random sort") on the collection. This way, you'll get all of the countries in one shot in a random order.

Community
  • 1
  • 1
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Ok, I'll probably rewrite the program completely to use all the advice presented here, because obviously my code is not idiomatic, to say the least. But could you please look over the code I posted and tell me why the program freezes upon reaching the last answer? I even tried to sneak my way out of the problem by just checking the "score" variable against Countries.Length, which should work, but doesn't. I'm sorry I'm posting it here in the comments, I don't know how to add a proper post without doing an EDIT of my initial one. – Leo Nov 15 '11 at 19:51
4

Instead of using two arrays, or an array and a list, let's introduce something of C# 4.0 that actually looks and is easy to use and seems to be made for this type of assignments.

Follow this code with your eyes and specifically look how these "anonymous types" are used in the end. It makes life real easy.

// initialize your array like so,
// now you can access your items as countries[1].name and countries[1].city
// and you will never have to worry about having too much cities or countries
// PLUS: they're always together!
var countries =  new [] {
    new { name = "The Netherlands", city = "Amsterdam"},
    new { name = "Andorra",         city = "Vaduz" }, 
    new { name = "Madagascar",      city = "Antananarivo"} 
};

// randomize by shuffling (see http://stackoverflow.com/questions/375351/most-efficient-way-to-randomly-sort-shuffle-a-list-of-integers-in-c-sharp/375446#375446)
Random random = new Random();
for (int i = 0; i < countries.Length; i += 1)
{
    int swapIndex = random.Next(i, countries.Length);
    if (swapIndex != i)
    {
        var temp = countries[i];
        countries[i] = countries[swapIndex];
        countries[swapIndex] = temp;
    }
}

// go through all your items in the array using foreach
// so you don't have to worry about having too much items
foreach(var item in countries)
{
     // show your girlfriend the country, something like
     string inputString = DisplayCountry(item.country);
     if(inputString == item.city)
     {
          ShowMessage("we are happy, you guessed right!");
     }
}


// at the end of the foreach-loop you've automatically run out of countries
DisplayScore(to-your-girlfriend);

Note: you can easily expand on this anonymous types by adding whether or not that particular country/city pair was guessed right and make a subsequent test with the ones she failed.

Abel
  • 56,041
  • 24
  • 146
  • 247
1

You could use a HashSet<int> to keep track of indexes that have been used. This won't accept duplicate values. The Add method returns a boolean that indicates whether the value was already in the list:

if (hashSet.Add(index))
    DisplayValue(index);
else
    //regenerate

But I would probably use your existing stragegy, but backwards: create a list pre-filled with values from 0 to Count - 1. Pick indexes from this list, removing them as you use them. This is logically similar to Reed Copsey's suggestion of sorting, but probably requires less change to your existing code.

var availableIndexes = new List<int>(Enumerable.Range(0, countryCount));
var random = new Random();
while (availableIndexes.Count > 0)
{
    var index = availableIndexes[Random.Next(0, availableIndexes.Count)];
    DisplayValue(index);
    availableIndexes.Remove(index);
}
phoog
  • 42,068
  • 6
  • 79
  • 117
1

Create a Country class and a Capital class.

Then model your classes to use a Dictionary<TKey, TValue> Generic Collection so that you declare the generic Dictionary object as:

Dictionary<Country, Capital>

where Country is the key and Capital is its value.

For MSDN reference to Dictionary and its sample usage, you can follow below link:

http://msdn.microsoft.com/en-us/library/xfhwa508.aspx

As you keep using Countries and Capitals, add them to above Dictionary instance after checking for their existence in the Dictionary instance, if any of them do exist then either popup an info message or a warning.

S2S2
  • 8,322
  • 5
  • 37
  • 65
1

You can use a key/value pair, like a Dictionary<string, string> to store your countries and capitals. Then iterate through the collection using a random LINQ orderby clause:

Dictionary<string, string> Countries = new Dictionary<int, string>();
// populate your collection of countries
foreach(var country in Countries.OrderBy(c => Guid.NewGuid()))
{
    Console.WriteLine("Key: {0}  Value: {1}", country.Key, country.Value);
}
Joel C
  • 5,547
  • 1
  • 21
  • 31
1

Quick and dirty, not necessarily efficient or secure.

Dictionary<string, string> countriesAndCapitals = new Dictionary<string, string>()
{
    { "Afghanistan", "Kabul" },
    { "Albania", "Tirane" },
    { "Algeria","Algers" },
    { "Andorra", "Andorra la Vella" } //etc, etc
};

foreach (var countryCapital in countriesAndCapitals.OrderBy(f => Guid.NewGuid()))
{
    Console.WriteLine(countryCapital.Key + " " + countryCapital.Value);
}
Bryan Crosby
  • 6,486
  • 3
  • 36
  • 55
1

It seems like what you need is a different type of data structure, two sets of lists would work fine but it is complicated for nothing. I suggest looking into the dictionary list type.

Dictionary<string,string> countryList = new Dictionary<string,string>();
countryList.Add("Canada","Ottawa");
countryList.Add("Thailand","Bankok");

etc...

You could then iterate through the list while a boolean value sees whether or not there was a hit. More info on Dictionary list type.

Philip
  • 11
  • 1
0

Why don't you remove the items from the list that you used? Then you don't have conflicts. Then you check states.Count() > 0.

Gerhard Powell
  • 5,965
  • 5
  • 48
  • 59
0

The quickest thing I can think to do is to use the Distinct() call on your list. Then your count of items in the list can be compared to your array's count to see if all have been used.

if(myUsedList.Distinct().Count() < myArray.Count) { ... }
Erik Dietrich
  • 6,080
  • 6
  • 26
  • 37