Chapter 3. Dictionaries

Dictionaries "To one" or "To many" that's the question.

In the last chapter, we have learnt about different generic list-based containers. Those are great for maintaining simple lists. However, they are not the best for storing associative relationships among different entities.

For example:

  • You might want to know how many electronic gadgets are available from amazon.com that are below $20

  • A football fanatic friend of mine wants to keep a count of how many times Real Madrid won over other teams in recent times

  • Your friend at the local electronics store dreams about a system that can auto-complete electronic part names

These are some of the examples you can think of, where you would need a mechanism to associate one variable with another. The fun factor is that all these situations can be represented conceptually using the same structure—the same structure where some fields will be accessible very quickly using some other unique variable. This structure is known as Dictionary in C#. Using Generics, you can build a common structure that can be used in different applications.

Reading this chapter and following the exercises, you shall learn the following:

  • Types of Generic Associative Structures and how to use them

  • How to create your own custom generic associative structure

Types of generic associative structures

There are two types of associative containers/structures available in .NET Generics. They are as follows:

  1. Key-value pairs

  2. Tuples

Key-value pairs can be of two types. One type allows duplicate keys. These are represented by a collection of KeyValuePair<TKey,TValue> objects. The other type doesn't allow duplicate keys. These are represented by Dictionary<TKey,TValue> and SortedDictionary<TKey,TValue>. SortedDictionary<TKey,TValue> keeps the keys in sorted order unlike Dictionary<TKey,TValue>, where the keys are not stored in any particular order.

Tuples are a new inclusion to the .NET 4.0 Framework and like KeyValuePair<TKey,TValue> they also allow duplicate keys. However, KeyValuePair<TKey,TValue> can be viewed as a special case of a Tuple. A Tuple allows you to store the relationship between n different variables (they may be of different types). However a Key-value pair is what the name suggests. It's a pair. Only one variable is associated with the other. If you need to store a relationship between three or more variables, you need a Tuple.

Creating a tag cloud generator using dictionary

Tag cloud is a fascinating way to visualize content. Tag clouds, generated from text sources, give an impression about the text. It speaks for the text.

Here is a tag cloud that I generated pointing my program to Apple's iPad page. It's almost evident from the tag cloud that the Apple iPad will arrive at the store from April and you can pre-order. All this without even rolling our eyeballs over the page:

Creating a tag cloud generator using dictionary

Time for action – creating the word histogram

Follow the given steps:

  1. Create a console application. Call it TagCloud.

  2. Add the following in the Main() method of Program.cs:

    Dictionary<string, int> wordHistogram = new Dictionary<string, int>();
    wordHistogram.Add("Apple",33);
    wordHistogram.Add("Orange",85);
    wordHistogram.Add("Strawberry",20);
    wordHistogram.Add("Watermelon",150);
    wordHistogram.Add("Guava", 52);
    wordHistogram.Add("Grape", 80);
    ShowCloud(wordHistogram);
  3. Copy the Sample Code from the following location and save it as TagCloudData.txt in the in the C: drive:

    http://visapi-gadgets.googlecode.com/svn/trunk/termcloud/doc.html

  4. Replace the highlighted part of this file (as shown in the following screenshot) with a place holder !DATA!:

    Time for action – creating the word histogram

    Delete the highlighted lines and replace these with !DATA!.

    So, after replacement it will look something similar to the following screenshot:

    Time for action – creating the word histogram
  5. Add the following method in Program.cs:

    private static void ShowCloud(Dictionary<string,int> wordHistogram)
    {
      //Creating the data to replace the placeholder
      StringBuilder cloudBuilder = new StringBuilder();
    
      cloudBuilder.AppendLine("data.addRows(" + wordHistogram.Keys.Count.ToString() + ")");
    
      int i = 0;
      foreach (string word in wordHistogram.Keys)
      {
    
        cloudBuilder.AppendLine("data.setValue(" + i.ToString()
        + ",0,'" + word + "'),");
        cloudBuilder.AppendLine("data.setValue(" + i.ToString()
        + ",1," + wordHistogram[word] + ");");
        i++;
      }
      //Replacing the placeholder 
      //If you can't put the file in C: drive. Put the file 
      //anywhere else where you have access
      StreamReader sr = new StreamReader("C:\TagCloudData.txt");
      string total = sr.ReadToEnd().Replace("!DATA!", cloudBuilder.ToString());
      sr.Close();
      //Writing the content to a temporary local file
      StreamWriter sw = new StreamWriter("C:\Cloud.html");
      sw.Write(total);
      sw.Close();
      //Showing the generated cloud. 
      System.Diagnostics.Process.Start("C:\Cloud.html");
    
    }
  6. Compile and run the application. You will need to be connected to the internet while using this application as it uses the Google Visualization API.

What just happened?

As you execute the program, it will launch your default browser and will show the following tag cloud:

What just happened?

Watermelon is the biggest tag, because it has the highest frequency assigned (150). A word histogram is nothing but a tabular view of a tag cloud. Every word has a weight. In the real world, the weight can be the frequency of occurrence in a text.

The declaration: Dictionary<string, int> wordHistogram = new Dictionary<string, int>(); creates a dictionary, where the keys are of type string and the value is of type integer. This dictionary is used to store the histogram of the words. As you see, Strawberry has the lowest weight and Watermelon has the largest. So, they appear smallest and largest in the tag cloud respectively.

We are using the Google Visualization API for generating the tag cloud. So, we somehow need to replace the hardcoded values from the sample HTML file. That's being done in the method ShowCloud().

All the keys can be obtained by the Keys property of the Dictionary class. It returns a KeyCollection object. Dictionary—similar to SortedList in the previous chapter—implements the IDictionary interface and thus, we can find the value associated with a key by indexing, using the key.

Thus, wordHistogram[word] will return the weight associated with the word in the dictionary wordHistogram.

Have a go hero

We have now created a console application to generate tag clouds from a single source. Can you extend the program, such that it can generate tag clouds from several text sources?

Hint: You can use a dictionary of dictionaries as follows:

Dictionary<string, Dictionary<string,int>>

The first string in the preceding dictionary is the type of key of the outer dictionary which represents the source file paths, and the inner dictionary stores the frequency of each word appearing in those files; or in other words histogram for those files.

Pop quiz

  1. Which structure would you use when mapping country versus capital?

    1. Dictionary<string,string>

    2. Dictionary<string,List<string>>

  2. Which structure would you use when mapping country versus state-wise per-capita income? Say, we represent per-capita income in decimal.

    1. Dictionary<string,decimal>

    2. Dictionary<string, Dictionary<string,decimal>>

    3. Dictionary<string,List<decimal>>

  3. How would you refer to the stock price of MSFT in the month of June 2011, if they are stored like this in a dictionary:

    Dictionary<string,Dictionary<string,double>> stockPrices = new Dictionary<string,Dictionary<string,double>>();
    
    stockPrices.Add("MSFT",new Dictionary<string,double>());
    stockPrices["MSFT"].Add("May2011",24.22);//Hypothetical stock price. 
    stockPrices["MSFT"].Add("June2011",34.22);//Hypothetical stock price. 

Creating a bubble wrap popper game

Sometimes we all get bored and need an easy break from boredom. Bubble wrap popping is one of the most popular games. It doesn't require any brain power, but it is still a lot of fun. I have seen people popping virtual bubbles for several reasons. It acts very well as a stress buster too. My wife loves this game.

This is a paradise for any bubble popper. It launches with a screen full of bubbles. As you click on a bubble it emits a realistic bubble pop sound and changes the look of the pattern such that it looks as if the bubble you just clicked is punctured. So it looks somewhat similar to the following screenshot. I have minimized it:

Creating a bubble wrap popper game

This is a simple game and we can make it using C# Dictionary. Let's get into action.

Time for action – creating the game console

Follow the given steps:

  1. Create a Windows application.

  2. Add the following code in Form1.cs:

    Dictionary<string, bool> bubbleStatus = new Dictionary<string,bool>();
    
    private void Form1_Load(object sender, EventArgs e)
    {
      //Lets find the width of the form
      int totalWidth = this.Width;
      //Lets find the height of the form
      int totalHeight = this.Height;
      //Lets see how many bubbles we can accommodate per row
      int perRow = totalWidth / 45;
      //Lets see how many bubbles we can accommodate per column
      int perCol = totalHeight / 45;
      //Bubbles will be images on picturebox controls
      //This is the first bubble. 
      PictureBox pic = new PictureBox();
      pic.Name = "pic0";
      pic.Cursor = System.Windows.Forms.Cursors.Hand;
      pic.Width = 45;
      pic.Height = 45;
      //Bubbles have to match the background image 
      //of the form for a realistic look
      pic.BackgroundImage =  
      Image.FromFile("C:\bubbleBackGround.jpg");
      //Loading the normal bubble image
      pic.ImageLocation = "C:\bubble.jpg";
      //Attaching a click event to handle the click.
      //When user clicks a bubble it should play the pop sound
      //and change the image to a popped bubble image to
      //create a more realistic special effect
      pic.Click += new EventHandler(pic_Click);
      //adding the first bubble to the form's control
      this.Controls.Add(pic);
      //Remembering where we painted the first bubble on the form
      Point lastLocation = pic.Location;
      //Making a copy, we are going to need this
      Point firstLocation = pic.Location ;
      //Adding this bubble to the dictionary. 
      //Right now nobody popped it
      //So the status is false. 
      bubbleStatus.Add(pic.Name, false);
      for (int r = 1; r <= perCol; r++)
      {            
        firstLocation = pic.Location;
        for (int c = 1; c <= perRow; c++)
        {
          //Creating bubbles on the fly
          pic = new PictureBox();
          pic.BackgroundImage = 
          Image.FromFile("C:\bubbleBackGround.jpg");
          pic.Name = "pic" + (r*10+c).ToString ();
          pic.Cursor = System.Windows.Forms.Cursors.Hand;
          pic.Width = 45;
          pic.Height = 45;
          pic.ImageLocation = "C:\bubble.jpg";
          pic.Click += new EventHandler(pic_Click);
          //Checking if There is already a bubble 
          //in the dictionary with this ID
          if (!bubbleStatus.ContainsKey(pic.Name))
          bubbleStatus.Add(pic.Name, false);
          else
          {
            //Change the ID arbitrarily. It doesn't 
            //have to be sequential
            //We are just going to need a way to 
            //access the status 
            //of a bubble given its ID
            pic.Name = pic.Name + c.ToString();
            //add this new bubble to the dictionary
            bubbleStatus.Add(pic.Name, false);
          }
          //Are we at the edge? 
          //If not we can still go on adding on the same column
          if (c % perRow != 0)
          pic.Location = new Point(lastLocation.X + pic.Width,lastLocation.Y);
          else
          //OOPs! we fell off the edge, 
          //we ran out of space to render any more 
          //bubbles in the current row. So lets go back to the second
          //row where we started. 
          pic.Location = 
          new Point(firstLocation.X , firstLocation.Y + pic.Height);
          //add the current bubble to the controls of the form
          this.Controls.Add(pic);
          //Remember where you added this last bubble. 
          lastLocation = pic.Location;
        }
      }
    
    }
    void pic_Click(object sender, EventArgs e)
    {
      PictureBox pic = (PictureBox)sender;
      if (bubbleStatus[pic.Name] == false)
      {
        //This bubble is Popped!!
        bubbleStatus[pic.Name] = true;           
        //Play bubble wrap pop sound
        System.Media.SoundPlayer p = 
        new System.Media.SoundPlayer("C:\BubblePop.wav");
        p.Play();
        //Change the image to give an impression that it //actually popped. 
        pic.ImageLocation = "C:\bubblePopped.jpg";
      }         
    }
  3. Now compile and run the program.

What just happened?

You should get a screen full of bubbles ready to be popped, as shown in the preceding screenshot.

Look how easy it was!

See, every bubble can either be normal or already popped. So the status of a bubble in the game board is binary. Thus, it is best to describe their status using a Boolean field. Now, how do we identify one bubble from the other? Well, every bubble has to have an ID. As we don't know how many bubbles we shall have, depending on the screen size the number may vary, so generating the ID at runtime seems an obvious choice.

How did we decide we need a dictionary and not a list?

We could also have done it using a list. However, if we did it using a list, identifying which bubble has just popped would have been time consuming and difficult as we would have needed a sequential search. Dictionary, on the other hand, offers very fast access as it keeps the entries indexed by the key. In this case, Key is the Name of the picture boxes that show the bubble images.

The dictionary: Dictionary<string, bool> bubbleStatus = new Dictionary<string, bool>(); stores the status of each bubble on the board. When a new bubble is created and gets its Name, then that is naturally not popped. At this point, the bubble is added to the dictionary as follows:

bubbleStatus.Add(pic.Name, false);

A false status means that the bubble has still not been popped. When the bubble is clicked, a check is made to see whether that bubble has already popped or not—by de-referencing the bubble status by its name—using the following code:

if (bubbleStatus[pic.Name] == false)

However, if this condition evaluates to be true, then the program changes the status of the bubble from false to true or conceptually from normal to popped if you assign the value true to the status using the following code snippet:

bubbleStatus[pic.Name] = true;           

I thought it would be a good idea to see how the dictionary stores the elements in runtime, here is a snapshot of the middle of a game:

How did we decide we need a dictionary and not a list?
How did we decide we need a dictionary and not a list?

Check that the first, third, fifth, and sixth bubbles are popped. Check their status from the dictionary. The first entry is highlighted.

You can see the game in action at http://sudipta.posterous.com/bubble-wrap-popper-in-c.

Let's build a generic autocomplete service

We live in an interesting time and remembering everything exactly could be very challenging. For example, imagine yourself in the following scenarios:

  • Coding in C# without intelligence support.

  • Trying to find an Integrated Circuit (IC) from the local electronics store for your school project, but you can't remember the exact number of the IC.

  • You work in a drugstore and a customer only knows first few letters of a drug. There are hundreds that start with those and you can't guess.

  • You work for a travel website and customers are expecting intelligent drop-downs, where city names get completed as they type.

All these situations are example of autocomplete feature that completes the entry for the user. It is easy to create a generic structure using the generic C# dictionary that can support autocomplete.

The objective of the design is that it has to be user driven. Once written, we should be able to support the autocomplete facility for any custom list of values.

Time for action – creating a custom dictionary for autocomplete

Follow the given steps:

  1. Create a class library project. Call it MyDictionary.

  2. Rename Class1.cs to MultiDictionary.cs.

  3. Modify the MultiDictionary class header as follows:

    public class MultiDictionary<Tkey,TValue> where Tkey : IComparable
  4. Add the following variable and the constructor:

    Dictionary<Tkey, List<TValue>> innerDS;
    
    public MultiDictionary()
    {
      innerDS = new Dictionary<Tkey, List<TValue>>();
    }
  5. Add the following method to add a single entry to the dictionary:

    public void Add(Tkey key, TValue val)
    {
      if (!innerDS.ContainsKey(key))
      {
        List<TValue> values = new List<TValue>();
        values.Add(val);
        innerDS.Add(key, values);
      }
      else
      {
        innerDS[key].Add(val);
      }
    }
  6. Add the following method:

    /// <summary>
    /// Find all the values for a given key
    /// </summary>
    /// <param name="key">The given key</param>
    /// <returns>Values associated with this key</returns>
    public IEnumerable<TValue> Values(Tkey key)
    {
      List<TValue> values = new List<TValue>();
      innerDS.TryGetValue(key, out values);
      return values;
    }
  7. Add the reference of MyDictionary to this console application project and add the following lines to the Program.cs:

    using MyDictionary;

    And in the Main() method , add the following code:

    static void Main(string[] args)
    {
      MultiDictionary<string, string> authorBookMap = new MultiDictionary<string, string>();
    
      authorBookMap.Add("Sudipta", "Data Structure using C");
      authorBookMap.Add("Sudipta", ".NET Generics Beginners Guide");
      var booksBySudipta = authorBookMap.Values("Sudipta");
      foreach (string bookTitle in booksBySudipta)
      Console.WriteLine(bookTitle);
    
      Console.ReadLine();
    }
  8. Compile and run the console application project.

What just happened?

As you execute the program, you will see the following output:

Data Structure using C
.NET Generics Beginners Guide

You might be thinking what's the point in discussing this in the autocomplete app? The reason is, for autocomplete we need to be able to map multiple entries to be associated with a single tag. In other words, we should be able to have duplicate keys in the structure.

Till now, we have used dictionaries to map one variable with another single variable. That's one-to-one mapping to be precise. However, for autocomplete, we need a one-to-many mapping capability. So, we should be able to map each key of the dictionary with a list of values. That's exactly what I did in this class MultiDictionary.

Pictorially MultiDictionary will look similar to the following diagram:

What just happened?

In the Add() method of the MultiDictionary class, it checks whether the Key is already present. If it is already present, we just add the value to the associated list of that Key. However, if it is not present, we create a blank list, add the new value to that list and then associate that list with the given key. However, the consumers of MultiDictionary get an impression that they can have duplicate keys.

Let's create an application showing how MultiDictionary can be used to offer an autocomplete feature.

Time for action – creating a class for autocomplete

Follow the given steps:

  1. Stay in the class library project where we created the MultiDictionary project. Add a class called AutoComplete.cs.

  2. Mark the class as public and add using System.IO; in the using directive list:

    public class AutoComplete
  3. Add the following variables and the constructor:

    List<string> values = new List<string>();
    MultiDictionary<string, string> autoCompleteMap;
    string sourceFile;
    int minimumLength;
    public AutoComplete(string file, int min)
    {
      autoCompleteMap = new MultiDictionary<string, string>();
      sourceFile = file;
      minimumLength = min;
    
      StreamReader reader = new StreamReader(sourceFile);
      string line = string.Empty;
      while ((line = reader.ReadLine()) != null)
      {
        if (line.Length > minimumLength)
        autoCompleteMap.Add(line.Substring(0, minimumLength), line);
      }
    
      reader.Close();
    }
  4. Add the following method:

    public List<string> GetSuggestions(string initial)
    {
      if (initial.Length == minimumLength)
      {
        values.Clear();
        List<string> vals = autoCompleteMap.Values(initial).ToList();
        values.AddRange(vals); 
      }
      if (initial.Length > minimumLength)
      {
        List<string> currentItems = new List<string>();
        foreach (object s in values)
        currentItems.Add(s.ToString());
        values.Clear();
        foreach (string k in currentItems)
        if (k.StartsWith(initial))
        values.Add(k); 
      }
      return values;
    }
  5. Now, create a Windows application to test this. Add a textbox (textBox1) as follows:

    Time for action – creating a class for autocomplete
  6. Now, add these two variables and add a reference to MyDictionary, and add the following using directive also:

    using MyDictionary;
    
    ListBox suggestionBox = new ListBox();
    AutoComplete suggester;
  7. Add code for the form_Load event as follows:

    private void Form1_Load(object sender, EventArgs e)
    {
      suggestionBox.Font = textBox1.Font;
      suggestionBox.DoubleClick+=new EventHandler
        (suggestionBox_DoubleClick);
      suggestionBox.KeyDown += new KeyEventHandler(suggestionBox_KeyDown);
      suggester = new AutoComplete("UK_Cities.txt", 2); 
    }
  8. Add the following event:

    private void textBox1_TextChanged(object sender, EventArgs e)
    {
      if (textBox1.Text.Length < 2)
      {
        suggestionBox.Visible = false;
        suggestionBox.Items.Clear(); 
      }   
      else
      {
        try
        {
          List<string> values = suggester.GetSuggestions(textBox1.Text);
          suggestionBox.Items.Clear();
          foreach (string value in values)
          suggestionBox.Items.Add(value);
          ShowListBox();
        }
        catch (Exception ex)
        {
          return;//Don't do anything.
        }
      }
    }
  9. Add the following method:

    private void ShowListBox()
    {
      suggestionBox.Location = 
      new Point(textBox1.Location.X, textBox1.Location.Y + textBox1.Height);
        
      suggestionBox.Width = textBox1.Width;
      suggestionBox.Height = 10 * textBox1.Height;
    
      suggestionBox.Visible = true;
              
      this.Controls.Add(suggestionBox);
      suggestionBox.BringToFront();        
    }
  10. Add the following events:

    void suggestionBox_KeyDown(object sender, KeyEventArgs e)
    {
      if (e.KeyData == Keys.Enter)
      {
        textBox1.Text = suggestionBox.Text;
        suggestionBox.Visible = false;
      }
    }
    
    private void suggestionBox_DoubleClick(object sender, EventArgs e)
    {
      textBox1.Text = suggestionBox.Text;
      suggestionBox.Visible = false;
    }
    
    private void textBox1_KeyDown(object sender, KeyEventArgs e)
    {
      if (e.KeyData == Keys.Down)
      {
        suggestionBox.Focus();
        if (suggestionBox.Visible)
        suggestionBox.SelectedIndex = 0;
      }
    }
  11. Compile and run the program.

What just happened?

As you execute the program, you will see the following output:

What just happened?

As you start typing, after two characters you shall received autocomplete suggestions, as shown in the following screenshot:

What just happened?

You can find a video of this output in my blog at: http://sudipta.posterous.com/auto-complete-feature-demo-video-written-in-c#.

The code described in steps 9 and 10 is for making the autocomplete feature more useful. As you press the down arrow key, it will set the focus on the listbox so that you can scroll for the right one in the suggestions. If you press Enter while one entry in the listbox is highlighted, that entry will be copied to the textbox and the same will happen if you double-click any item in the listbox. These make the program more real-world ready.

The heart and soul of this program is the following code snippet:

suggester = new AutoComplete("UK_Cities.txt", 2);

This creates an AutoComplete object. This tells the program that it must populate the entries from the file Uk_Cities.txt and it should offer suggestions as soon as the first two characters are typed in the textbox.

In the constructor of the AutoComplete class, the following code snippet:

autoCompleteMap.Add(line.Substring(0, minimumLength), line);

stores the words in a MultiDictionary where Keys of the MultiDictionary are prefixes of length minimumLength. One entry in this MultiDictionary is as follows:

What just happened?

The most interesting method is the GetSuggestion() method as it calculates the list of suggestions with every changing few initial letters. If the length of the initial text is more than the minimum length for offering a suggestion, then the already suggested strings are filtered depending on whether it starts with the same pattern as the input string.

For example, as soon as you type Gl in the box, you will see Glasgow and Gloucester in the list as they both start with Gl. However, if you keep typing, then the input string of Gla will only match Glasgow and show that in the suggestion box.

While we deal with dictionaries, we should try to avoid the biggest possible error that might crash a program at runtime. Let's see how.

The most common pitfall. Don't fall there!

The most common pitfall is what is known as a hole problem. It means you are trying to access the value for a key from a dictionary which doesn't exist. This throws a KeyNotFoundException. In order to avoid this, you can use the TryGetValue() method that first checks whether the key is present and then tries to de-reference that.

Here is a typical use:

Dictionary<string, int> histogram = new Dictionary<string, int>();
histogram.Add("a", 1);
histogram.Add("b", 2);

//The following call will throw KeyNotFoundException
int x = histogram["c"];int y;
//The following call will set y to default value for int, which is 0
histogram.TryGetValue("c", out y);

Let's play some piano

The piano is one of the closest analogies of a C# Dictionary (at least conceptually) in the real world. Every key of the piano is associated with a particular note. In this example, we shall create a table-top piano with 12 keys (A, B, C, D, E, F, G, B#, C#, E#, F#, G#) as shown in the following screenshot:

Let's play some piano

This would have the following functionalities:

  • Users should be able to play it by clicking on the keys

  • Users should be able to play it by pressing keys on the keyboard

  • Users should be able to customize the key settings to their preference

  • It should be able to record a note and play it back

  • It should show labels for each key on demand (as shown in the preceding screenshot)

The preceding screenshot might look very fancy, but it is composed with only winform buttons and labels. I have skipped the details for laying out the controls and their event handlers. However, you can find all that from the website including the background image and the .wav files used.

Time for action – creating the keys of the piano

Follow the given steps:

  1. Create a Windows form. Place the controls as shown in this video: http://sudipta.posterous.com/private/eweuvoFJwb. Eventually, your layout should look like the one shown in the preceding screenshot. As it is a complex layout, I have created the video to help you.

  2. Add the following variables to Form1.cs:

    bool startRecording = false;

    This will be set to true when the app starts recording keystrokes.

  3. Add the following field to the Form1.cs:

    string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    
    Dictionary<string, string> pianoNotes = new Dictionary<string, string>();
    
    Dictionary<string, char> keySettings = new Dictionary<string, char>();
    
    List<KeyValuePair<string, DateTime>> keyPressRecord = new List<KeyValuePair<string,DateTime>>();
  4. Add the following methods:

    /// <summary>
    /// Plays the .WAV file associated with the pressed key
    /// </summary>
    /// <param name="pianoKey">The piano key that is pressed</param>
    
    private void PlayPiano(string pianoKey)
    {
      try
      {
        System.Media.SoundPlayer p = new System.Media.SoundPlayer(pianoNotes[pianoKey]);
        p.Play();
      }
      catch (KeyNotFoundException ex)
      {
        return;
      }
    }
    
    /// <summary>
    /// This method fills the key settings 
    /// with recommended values by yours truly!
    /// Every time the program starts these values will be loaded.
    /// So even if the users make any customization, for now, that 
    /// will not be sticky.
    /// </summary>
    
    private void FactoryReset()
    {
      keySettings.Add("A", 'A'),
      keySettings.Add("B", 'B'),
      keySettings.Add("C", 'C'),
      keySettings.Add("D", 'D'),
      keySettings.Add("E", 'E'),
      keySettings.Add("F", 'F'),
      keySettings.Add("G", 'G'),
      keySettings.Add("B#", 'Q'),
      keySettings.Add("C#", 'W'),
      keySettings.Add("E#", 'R'),
      keySettings.Add("F#", 'T'),
      keySettings.Add("G#", 'H'),
    }
    
    /// <summary>
    
    /// Populates the drop down boxes with the recommended keys
    /// </summary>
    
    private void PopulateKeys()
    {
      cmbA.Text = alphabet[alphabet.IndexOf(keySettings["A"])].ToString();
      cmbB.Text = alphabet[alphabet.IndexOf(keySettings["B"])].ToString();
      cmbC.Text = alphabet[alphabet.IndexOf(keySettings["C"])].ToString();
      cmbD.Text = alphabet[alphabet.IndexOf(keySettings["D"])].ToString();
      cmbE.Text = alphabet[alphabet.IndexOf(keySettings["E"])].ToString();
      cmbF.Text = alphabet[alphabet.IndexOf(keySettings["F"])].ToString();
      cmbG.Text = alphabet[alphabet.IndexOf(keySettings["G"])].ToString();
      cmbBSharp.Text = alphabet[alphabet.IndexOf(keySettings["B#"])].ToString();
      cmbCSharp.Text = alphabet[alphabet.IndexOf(keySettings["C#"])].ToString();
      cmbESharp.Text = alphabet[alphabet.IndexOf(keySettings["E#"])].ToString();
      cmbFSharp.Text = alphabet[alphabet.IndexOf(keySettings["F#"])].ToString();
      cmbGSharp.Text = alphabet[alphabet.IndexOf(keySettings["G#"])].ToString();
    }
  5. Add the following code in Form_Load:

    private void Form1_Load(object sender, EventArgs e)
    {
      //Associating the .WAV files with the keystrokes
      pianoNotes.Add("A", "A.wav");
      pianoNotes.Add("B", "B.wav");
      pianoNotes.Add("B#", "B#.wav");
      pianoNotes.Add("C", "C.wav");
      pianoNotes.Add("C#", "C#.wav");
      pianoNotes.Add("D", "D.wav");
      pianoNotes.Add("E", "E.wav");
      pianoNotes.Add("E#", "E#.wav");
      pianoNotes.Add("F", "F.wav");
      pianoNotes.Add("F#", "F#.wav");
      pianoNotes.Add("G", "G.wav");
      pianoNotes.Add("G#", "G#.wav");
      FactoryReset();
      PopulateKeys();
    }
  6. Add event handlers for all the buttons (representing piano keys) as follows:

    private void btnA_Click(object sender, EventArgs e)
    {
      //Showing what key is pressed.
      lblKeyStroke.Text = "A";
      PlayPiano("A");
      //If user intended to start recording,
      //let's remember this key stroke.
      if (startRecording)
      keyPressRecord.Add (new KeyValuePair<string, DateTime>("A", DateTime.Now));
    }
  7. The helper button's name is btnShowLables and add the following event handler for that:

    private void btnShowLables_Click(object sender, EventArgs e)
    {
      if (btnShowLables.Text == "Help")
      {
        keyLabelA.Visible = true;
        keyLabelB.Visible = true;
        keyLabelC.Visible = true;
        keyLabelD.Visible = true;
        keyLabelE.Visible = true;
        keyLabelF.Visible = true;
        keyLabelG.Visible = true;
        keyLabelBSharp.Visible = true;
        keyLabelCSharp.Visible = true;
        keyLabelESharp.Visible = true;
        keyLableFSharp.Visible = true;
        keyLabelGSharp.Visible = true;
        grpKeySettings.Visible = true;
        btnShowLables.Text = "Helping";
      }
      else
      {
    
        keyLabelA.Visible = false;
        keyLabelB.Visible = false;
        keyLabelC.Visible = false;
        keyLabelD.Visible = false;
        keyLabelE.Visible = false;
        keyLabelF.Visible = false;
        keyLabelG.Visible = false;
        keyLabelBSharp.Visible = false;
        keyLabelCSharp.Visible = false;
        keyLabelESharp.Visible = false;
        keyLableFSharp.Visible = false;
        keyLabelGSharp.Visible = false;
        grpKeySettings.Visible = false ;
        btnShowLables.Text = "Help";
      }
    }

What just happened?

Once you compile and run the application, it will show the piano interface. You can click on each key to play.

Now, let's see what our code can do so far.

These two dictionaries: Dictionary<string, string> pianoNotes = new Dictionary<string, string>(); and Dictionary<string, char> keySettings = new Dictionary<string, char>(); are the heart and soul of this application.

The first one, pianoNotes, associates a piano key to a given .wav file. So, as you click on that button, that associated media file (.wav) could be played.

The second one, maps the keyboard keys to the piano keys. For example, there is no key on the keyboard to directly show "C#". So the key W is mapped to "C#". All these are being done in the FactoryReset() method.

As soon as you click on any button, the PlayPiano() method plays the associated media file. This is done using key-based indexing of Dictionaries. For example, if you click on btnA then pianoNotes[pianoKey] will return the value of the key A in the dictionary pianoNotes, which is A.wav.

Now, suppose you want to play the "C#" note. Which keyboard key should you click on? The answer is you should click btnW or press W on the keyboard; which is the associated keyboard key for piano key "C#".

This type of single unique key and value association is known as one-to-one mapping. And the type of dictionary we created for autocomplete is called one-to-many mapping.

How are we recording the key strokes?

Well, we are using List<KeyValuePair<string, DateTime>> keyPressRecord = new List<KeyValuePair<string,DateTime>>(); for that. As the same piano note can be played many times during a session, we can't use a dictionary to record keystrokes, because the dictionary can't have duplicate keys. So, the solution is a list of KeyValuePair<TKey,TValue> class objects that allows us to store KeyValuePair with the same key. The value type is DateTime, because we need to remember exactly when which key was pressed, if we need to play it back. The duration between each such key press will be determined by finding the difference between the values of consecutive DateTime items.

When I played and recorded my keystrokes, for a while it was stored in the list of key-value pairs as follows:

How are we recording the key strokes?

So you see, I waited two seconds after I hit the first key A. So while playing it back, we must play the second note (which is B in this case) after two seconds of playing the first note.

Let's see how we can do that!

Time for action – switching on recording and playing recorded keystrokes

Follow the given steps:

  1. Add the following method in Form1.cs:

    /// <summary>
    /// Switch on recording. 
    /// </summary>
    private void btnRecord_Click(object sender, EventArgs e)
    {
      if (btnRecord.Text.Equals("Record"))
      {
        //let's remember this key stroke.
        keyPressRecord.Clear();
        //We need to start recording.
        startRecording = true;
        btnRecord.Text = "Stop";
      }
      else
      {
        startRecording = false ;
        btnRecord.Text = "Record";
      }
    }
  2. Add the following method and event in Form1.cs:

    private void SleepInBetween(TimeSpan span)
    {
      System.Threading.Thread.Sleep(span.Minutes);
      System.Threading.Thread.Sleep(span.Seconds);
      System.Threading.Thread.Sleep(span.Milliseconds);
    }
    
    /// <summary>
    /// Plays the recorded keystrokes
    /// </summary>
    
    private void btnPlay_Click(object sender, EventArgs e)
    {
      try
      {
        int i;
        TimeSpan span;
        for (i = 0; i < keyPressRecord.Count - 1; i++)
        {
    
          PlayPiano(keyPressRecord[i].Key);
          span = keyPressRecord[i + 1].Value
          .Subtract(keyPressRecord[i].Value);
          //Lets wait till the timespan between 
          //these two key-strokes are spent.
          SleepInBetween(span);  
        }
        span = keyPressRecord[i].Value.Subtract(keyPressRecord[i - 1].Value); 
        SleepInBetween(span);
        PlayPiano(keyPressRecord[i].Key);            
      }
      catch (Exception ex)
      {
        //Let's go back
        return;
      }
    }

How it works?

We are iterating over the recorded keystrokes using this loop: for (i = 0; i < keyPressRecord.Count - 1; i++). keyPressRecord[i].Key gives the ith key pressed by the user from the start.

keyPressRecord[i + 1].Value.Subtract(keyPressRecord[i].Value); returns the time difference between the two consecutive recorded keystrokes. The program needs to sleep during this time. Once we reach out of this loop, we need to play the last recorded keystroke. You can access the key and value of a KeyValuePair by public property Key and Value.

You can see the piano being played at http://sudipta.posterous.com/my-table-top-piano-12-keys.

C# Dictionaries can help detect cancer. Let's see how!

Now that you are aware of the KeyValuePair, let's see another interesting situation where this structure can be used.

Pattern recognition is an emerging science. K Nearest Neighbor (popularly known as KNN) is a supervised learning algorithm that can be used in binary classification problems very efficiently.

A binary classification problem is just what it states. It is a classification problem. Typically, classification of several entries are previously known and depending on that a new entry has to be classified in either of the two categories. Thus it is called binary.

Suppose we have records of several patients who are diagnosed with either malignant (class 'M' cancerous) or benign (class 'B' not cancerous) cases. Now, we gather details from the tests for a new patient. Depending on what we know previously from past patient records, can we classify the new patient's case as either a malignant case or a benign case? That's where binary classification helps in real life.

KNN uses a voting mechanism to solve this problem. It considers test results as a vector. If two vectors, representing two patient's test records, are near then they are probably suffering from the same type of case (either benign or malignant). Now this mechanism can lead to confusing results sometimes, if only the nearest vector is considered. So, K nearest vectors are considered. Thus the name KNN.

In this example, we shall see how C# Dictionary-based data structures can be used to solve this problem.

Time for action – creating the KNN API

Follow the given steps:

  1. Create a class library project.

  2. Add a class to the project. Call it Entry. Add the following code:

    public class Entry
    {
      public string Id
      {
        get; 
        set; 
      }
      public string Tag
      {
        get;
        set;
      }
      public List<double> ParamValues
      {
        get; 
        set; 
      }
    }
  3. Add another class to this project. Call it KNN.

  4. Add the following variable and the constructor:

    private List<KeyValuePair<string, List<double>>> map ;
    public KNN()
    {
      map = new List<KeyValuePair<string, List<double>>>();
    }
  5. Add the following methods:

    /// <summary>
    /// Adds a patient record to the list
    /// </summary>
    /// <param name="entry"></param>
    public void Add(Entry entry)
    {
      map.Add(new KeyValuePair<string,List<double>>(entry.Tag, entry.ParamValues));
    }
    /// <summary>
    /// Adds an entry to the list
    /// </summary>
    /// <param name="tag">Class of the new entry</param>
    /// <param name="entries">Patient Records</param>
    
    public void Add(string tag, List<double> entries)
    {
      map.Add(new KeyValuePair<string, List<double>>(tag, entries));
    }
    /// <summary>
    /// Calculates the Euclidean Distance between two entries
    /// </summary>
    /// <param name="index1">Index of the first entry in the /// list</param>
    /// <param name="index2">Index of the second entry in the /// list</param>/// <returns>
    ///  To know more about Euclidean Distance please refer/wiki/Euclidean_distance
    ///  </returns>
    private double distance(int index1,int index2)
    {
      double sum = 0;  
      for (int i = 0; i < map[index1].Value.Count; i++)
      {
        sum += Math.Pow(Math.Round(map[index1].Value[i] – map[index2].Value[i],2),2);
      }
      return Math.Round(Math.Sqrt(sum),2);
    }
  6. Add the following method:

    /// <summary>
    /// Predicts the suspected class of the input data 
    /// </summary>
    /// <param name="entries">Values. In this case value of several /// parameters</param>
    /// <param name="class1">The first of the binary classes this data /// set can belong to. In this example, either "B" or "M" /// </param>
    /// <param name="class2">The second of the binary classes this /// data set can belong to. In this example either "B" or "M" /// </param>
    /// <param name="k">Number of nearest neighbor for we need to /// consider to conclude a class.</param>
    /// <remarks>For more information visit http://en.wikipedia.org//// wiki/K-nearest_neighbor_algorithm</remarks>
    public string Predict(List<double> entries, string class1, string class2, int k)
    {
      //Dictionary to keep the entries indexed and sorted by their 
      //distance from the new values
      SortedDictionary<double, string> distanceMap = new SortedDictionary<double, string>();
      int count = 0;
    
      this.Add("X", entries);//Right now it is unknown
      int lastIndex = map.Count - 1;
      int firstIndex = 0; //Lets start counting at 0
      double minDistance = distance(firstIndex, lastIndex);
      for (int i = firstIndex + 1; i < lastIndex - 1; i++)
      {
    
        double currentDistance = distance(i, lastIndex);
        if(!distanceMap.ContainsKey(currentDistance))
        distanceMap.Add( currentDistance,map[i].Key);                   
      }
    
      map.RemoveAt(map.Count - 1);//lets remove the last entry
      Dictionary<double, string> kVals = new Dictionary<double, string>();
    
      foreach (double key in distanceMap.Keys)
      {
        if (count == k)
        break;
        kVals.Add(key, distanceMap[key]);
        
      }
      //Finding Class of the new entry "class1"
      int class1Count = 0;
      int class2Count = 0;
      foreach (double key in kVals.Keys)
      {
        if (kVals[key] == class1)
        class1Count++;
        else
        class2Count++;
      }
      return class1Count > class2Count ? class1 : class2;           
    }

What just happened?

You can compile the project but can't run it yet. It doesn't have any data. We shall get to that in a while. Let's see what we created.

The class Entry represents a particular patient's record. Records from different tests are obtained and we are using numeric record values.

The class KNN implements the KNN algorithm. The inner data structure of the KNN class is: List<KeyValuePair<string, List<double>>>.

Let's see why. We have a list of patient records, where the patients might be suffering from either of the two cases and there could be multiple patients suffering from the same class of disease. So a plain dictionary is ruled out as it can't support duplicate keys. We can't use a MultiDictionary—described earlier in the chapter—because we would need integer indexing. So, we need a key-value pair list where the key is the class of disease and the value is the list of values of different test results.

So pictorially, we are using the structure shown in the following screenshot:

What just happened?

The Predict() method is doing all the magic. It predicts the possible class of the new patient entry. It uses a sorted dictionary to keep a list of entries in the ascending order of their distance from the new entry:

SortedDictionary<double, string> distanceMap = new SortedDictionary<double, string>();

This dictionary keeps the class tags and the distance of the patient's record data entries from the newly-added unidentified one, ordered by the distance because tags will be duplicate, however, distance can't be in most instances. And if they are, we don't need to add that entry to the dictionary as it actually is a duplicate entry.

So the entries in this dictionary will look similar to the following:

What just happened?

The index on the left is the value for k. So the first entry k=1 is the closest to an entry which is of type M.

Once we have this dictionary in the Predict() method, we need to consider only the first k entries of the SortedDictionary distanceMap. If the number of entries among these first k elements having tag value class1 is more than that of entries having tag value class2, then the new unidentified entry will be marked as class1 else it will be marked as class2. That's how the voting works.

Time for action – getting the patient records

Follow the given steps:

  1. Download patient records from the following website:

    http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data..

    If you are interested to know what are these values check out: http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.names.

  2. Copy about one-third of the entries to another file and delete them from this one. Save the new file as wdbctest.txt and the left over entries in the original file as wdbc.txt. You can get these files from the book website.

Time for action – creating the helper class to read a delimited file

Follow the given step:

  1. Stay in the class library project and add a class called TextReader.cs:

    public class TextReadHelper
    {
      private string fileName;//Delimited source file that has // patient records
      private int tagIndex; //Index of the class of disease this // patient is suffering 
      private int idIndex;//Index of the ID of the patient in // the delimited file
      string delim; //delimiter 
      public TextReadHelper(string fileName,int tagIndex,int idIndex,string  delim)
      {
        //id,tag,values
        //tag,values
        this.fileName = fileName;
        this.tagIndex = tagIndex;
        this.idIndex = idIndex;
        this.delim = delim;
      } 
      /// <summary>
      /// Returns the list of patient records
      /// </summary>
      public List<Entry> GetEntries()
      {
        //Include System.IO for this line of code
        StreamReader sr = new StreamReader(fileName);
        string line = string.Empty;
        List<Entry> entries = new List<Entry>();
        while ((line = sr.ReadLine()) != null)
        {
          string[] tokens = line.Split(new string[] {delim}, StringSplitOptions.RemoveEmptyEntries);
          Entry current = new Entry();
          if(idIndex!=-1)
          current.Id = tokens[idIndex];
          current.Tag = tokens[tagIndex];
          current.ParamValues = new List<double>();
          for (int i = 2; i < tokens.Length; i++)
          current.ParamValues.Add(Convert.ToDouble(tokens[i]));
          entries.Add(current);
        }
        sr.Close();
        return entries;
      }
    }
    

What just happened?

We wanted to read records of patient data from delimited notepad files. So, we needed a mechanism to be able to read these files and convert them to a list of Entry objects, which represents patient data.

With this, we are now ready to put it all together.

Time for action – let's see how to use the predictor

  1. Create a console application and add a reference of the class library we built earlier.

  2. Add the following using directive at the beginning:

    using ClassLibrary1;
  3. Add the following code in the Main() method of the console application:

    static void Main(string[] args)
    {            
      TextReadHelper helper = new TextReadHelper("C:\wdata.txt",1,0,",");
      KNN knnHelper = new KNN();
      List<Entry> entries = helper.GetEntries();
    
      foreach (Entry current in entries)
      knnHelper.Add(current);
           
      int k = 3;
      string tag = knnHelper.Predict(new List<double>(){21.56, 22.39, 142, 1479, 0.111, 0.1159, 0.2439, 0.1389, 0.1726, 0.05623, 1.176, 1.256, 7.673, 158.7, 0.0103, 0.02891, 0.05198, 0.02454,0.01114, 0.004239, 25.45, 26.4, 166.1, 2027, 0.141, 0.2113, 0.4107, 0.2216, 0.206,0.07115 },"M", "B", k);
    
      Console.WriteLine("Suspected diagnoses  " + tag);
      Console.ReadLine();            
    }
  4. Compile and run this console application.

What just happened?

As you execute the program, it will print the following output:

Suspected diagnoses  M

The call to the Predict() method takes only a new patient's test data record, saves the patient ID and tag. k is set to 3. So in this case, we are considering three nearest neighbors for reaching a conclusion about the possible disease class the new patient is suffering from.

I thought it would be a good idea to see the final dictionary of distance mapped entries. So, here it is for the call we made from Main():

What just happened?

Tuples are great for many occasions including games

Tuples are a new inclusion in the .NET 4.0 framework and they are a perfect fit for many situations, some of them are as follows:

  • To represent a database table row in the code.

  • To replace all those dummy classes that have no methods and are just acting as placeholders for several types of objects.

  • To bring association between more than a couple of types. Have you ever found yourself using Dictionary of Dictionaries? Then you shall find Tuples really useful.

Tuples can be used to refactor a nested branching statement. In this example, we shall create a number rearranging game that I used to play in my childhood.

Time for action – putting it all together

Follow the given steps:

  1. Create a Windows application. Call it TilO.

  2. Add nine button controls. Also add one label control and one pictureBox control, as shown in the following screenshot. Add large borders to buttons ("10"), make them flat, and name them as follows:

    Time for action – putting it all together
  3. Add the following variables in the Form1.cs:

    bool mute = false;
    int totalMoves = 0;
                    
    List<Tuple<int, int, int>> board = new List<Tuple<int, int, int>>();
    Dictionary<int, Button> buttons = new Dictionary<int, Button>();
  4. Add the following methods to draw the board properly. You can customize the colors:

    private void DrawBoard()
    
    {
      int emptyIsAt = board.First(t => t.Item3 == 0).Item1;
      DrawEmpty(emptyIsAt);
    
      //Draw Rest
      for (int i = 0; i < board.Count; i++)
      {
        if (board[i].Item3 != 0)
        {
          DrawOthers(board[i].Item1, board[i].Item3);
        }
      }
    }
            
    private void DrawOthers(int index,int number)
    {
      buttons[index].Text = number.ToString();
      buttons[index].BackColor = Color.White;
    }
            
    private void DrawEmpty(int index)
    {
      buttons[index].BackColor = Color.Black;
      buttons[index].Text = string.Empty;
    }
  5. Add the following methods to check whether the tiles the players want to swap can actually move or not. If so, the second method DoMove() handles the movement:

    private bool CanMove(int first, int second)
    {
      if (first != 0 && second != 0)
      return false;
      int firstIndex = board.First(tuple => tuple.Item3 == first).Item1;
      int secondIndex = board.First(tuple => tuple.Item3 == second).Item1;
      int absDiff = Math.Abs(firstIndex - secondIndex);
      //Handle Edge cases
      if (absDiff == 1 && Math.Min(firstIndex, secondIndex) % 3 == 0)
      return false;
      if (absDiff == 1 || absDiff == 3)
      return true;
      else
      return false; 
    
    }
    
    private void DoMove(string moveCommand, bool silent)
    {
      string[] moveThese = moveCommand.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
    
      //If some player just clicks the Empty box then it will //generate 
      //an invalid command "0". And in this time moveThese will be //of length 1.
      if (moveThese.Length == 2)
      {
        int first = Convert.ToInt16(moveThese[0]);
        int second = Convert.ToInt16(moveThese[1]);
        bool CanTheseBeMoved = CanMove(first, second);
        if (CanTheseBeMoved)
        {
          //Move
          int firstIndex = board.First(t => t.Item3 == first).Item1;
          int secondIndex = board.First(t => t.Item3 == second).Item1;
          int expectedFirst = board.First(t => t.Item3 == first).Item2;
          int expectedSecond = board.First(t => t.Item3 == second).Item2;
          Tuple<int, int, int> newFirstTuple = new Tuple<int, int, int>(firstIndex, expectedFirst, second);
                  
          Tuple<int, int, int> newSecondTuple = new Tuple<int, int, int>(secondIndex, expectedSecond, first);
                   
          board.RemoveAt(firstIndex - 1);
          board.Insert(firstIndex - 1, newFirstTuple);
               
          board.RemoveAt(secondIndex - 1);
          board.Insert(secondIndex - 1, newSecondTuple);
           
          if (!mute)
          {
            System.Media.SoundPlayer player = new 
            System.Media.SoundPlayer("Blip.wav");
            player.Play();
          }
          totalMoves++;
          lblMoves.Text = String.Format("You have made {0} moves so far.", totalMoves);
        }
        else
        {
    
          if (!silent && !mute)
          {
            System.Media.SoundPlayer player = new 
            System.Media.SoundPlayer("Error.wav");
            player.Play();
          }
        }
      }
    }
  6. Add the following methods to initialize board and buttons:

    private void InitializeStartUpBoard()
    {
      board.Add(new Tuple<int, int, int>(1, 1, 8));
      board.Add(new Tuple<int, int, int>(2, 2, 7));
      board.Add(new Tuple<int, int, int>(3, 3, 6));
      board.Add(new Tuple<int, int, int>(4, 4, 5));
      board.Add(new Tuple<int, int, int>(5, 5, 4));
      board.Add(new Tuple<int, int, int>(6, 6, 3));
      board.Add(new Tuple<int, int, int>(7, 7, 2));
      board.Add(new Tuple<int, int, int>(8, 8, 1));
      board.Add(new Tuple<int, int, int>(9, 0, 0));
    }
    
    private void InitializeNumberButtonMap()
    {
      buttons.Add(1, btn1);
      buttons.Add(2, btn2);
      buttons.Add(3, btn3);
      buttons.Add(4, btn4);
      buttons.Add(5, btn5);
      buttons.Add(6, btn6);
      buttons.Add(7, btn7);
      buttons.Add(8, btn8);
      buttons.Add(9, btn9);
    }
  7. Add these event handlers to handle the events of button Click and volume control pictureBox Click:

    void but_Click(object sender, EventArgs e)
    {
      Button button = (Button)sender;
      DoMove(String.Format("{0}=0", button.Text), false);
      DrawBoard();
      GameOverYet();
    }
    
    //This is for volume toggle. You can skip it.
    private void pictureBox1_Click(object sender, EventArgs e)
    {
      if (mute)
      pictureBox1.ImageLocation = @"Volume_img.jpg";
      else
      pictureBox1.ImageLocation = @"Mute_img.JPG";
      //Switch the state
      mute = !mute;
    }
  8. Add the following code to randomize the board. Getting the same initial board every time is not what we want:

    private void RandomizeBoard()
    {
      //generate random move commands and move them whenever possible
      //finally return the modified board
      string randomMoveCommand = string.Empty;
      int total = new Random().Next(100);
      for (int i = 0; i < total; i++)
      {
        try
        {
          randomMoveCommand = new Random().Next(9).ToString() + "=0";
          System.Threading.Thread.Sleep(10);
          DoMove(randomMoveCommand, true);
        }
        catch
        {
          //This randomly generated move is illegal. Don't worry, just go.
          continue;
        }
      }
    
    }
  9. Add the following code for the Form1_Load event:

    private void Form1_Load(object sender, EventArgs e)
    {
      //Attach the event handler for al the buttons
      this.Controls.OfType<Button>().ToList()
      ForEach(but => but.Click+=new EventHandler(but_Click));
        
      //Show the Volume Control image
      pictureBox1.ImageLocation = @"Volume_img.JPG";
    
      buttons.Clear();
      board.Clear();
      InitializeNumberButtonMap();           
      InitializeStartUpBoard();
      RandomizeBoard();            
      lblMoves.Text = "How many moves do you think ?";
      //When we were randomizing the board, there were some moves //(probably)
      //But we want to give the player a fresh start. 
      //So set the total Moves count to Zero.
      //totalMoves = 0;
      //Draw the initial Board
      DrawBoard();
    }
  10. Add the following method:

    private void GameOverYet()
    {
      if (board.Where(tuple => tuple.Item2 != tuple.Item3).Count() == 0)
      {
        MessageBox.Show("Game Over! Congratulations!");
      }
    }

What just happened?

As you execute the program, you will see something similar to the following screenshot:

What just happened?

If you click on any of the numbered tiles, if it is adjacent to the black empty tile, it will swap its position with the empty tile. For example, in this case 5, 7, 4, and 1 are only eligible to swap their positions. As a tile moves, it will emit a sound. If you don't want sound, click on the speaker icon at the bottom-right.

You can take a look at the completed game demo video at my blog: http://sudipta.posterous.com/number-re-arrange-game-an-application-of-tupl.

Why have we used Tuples?

The board at any time can be represented as a list of three different integers attached together:

  1. The index of the tile

  2. The expected value of the tile in order for the game to end

  3. The actual value of the tile in that index right now

Note

Tips!

Tuples are a great way of representing database tables. There are eight overloaded versions of Tuple constructors. You can use one of them or use the static Create() method of the Tuple class. The last parameter for the last overload is another Tuple itself. So, you can plug in another Tuple if you need more than seven parameters.

For example, the board we have now and our goal (to finish the game) board are as follows:

Why have we used Tuples?

If we had to represent this without Tuples, we would have had to create a dump placeholder class with three values and create a list of such class objects. But the construct: List<Tuple<int, int, int>> board = new List<Tuple<int, int, int>>(); helps remove that special need. A list of Tuples represents the game board in this case. The first item of the Tuple is the index of the tile, the second is the expected numeric value on that tile, the third is the actual numeric value on that tile right now.

The InitializeStartUpBoard() method initializes the board with some default values. The next RandomizeBoard() method randomizes the board using some randomly generated moves. Some of these random moves might/will fail and that's ok.

Here is how the board looks after initialization is complete and randomization is yet to start:

Why have we used Tuples?

One disadvantage some people find using Tuples is that they reduce the readability of the code, because we can't name the items.

The method DoMove() moves two tiles visually. Actually, it swaps those two Tuples with changed entries. The following code:

int firstIndex = board.First(t => t.Item3 == first).Item1;

finds the index of the first integer in the command passed. So, if the command passed is 3=0 then first is 3 and firstIndex will store the index of tile with text 3 in the board.

First() is an extension method. We shall discuss this in detail in the next chapter, but for now just use it as it makes the code much cleaner.

The following lines:

Tuple<int, int, int> newFirstTuple = new Tuple<int, int, int>(firstIndex, expectedFirst, second);
Tuple<int, int, int> newSecondTuple = new Tuple<int, int, int>(secondIndex, expectedSecond, first);

create two Tuples with swapped actual values. Now, the older Tuples are deleted and replaced with these new Tuples in their positions by the following lines:

board.RemoveAt(firstIndex - 1); //removes the old tuple 
board.Insert(firstIndex - 1, newFirstTuple); //Add the new tuple

board.RemoveAt(secondIndex - 1);
board.Insert(secondIndex - 1, newSecondTuple);

Inserting at the correct position is very important because the DoMove() method relies on it.

How did we figure out whether the game is over or not?

The game will be over if Item2 and Item3 for each Tuple on the board become the same. Because Item2 is the expected value of the tile and Item3 is the current value, if there is no such tile that has a mismatch between Item2 and Item3, then we can conclude that the game is over.

board.Where(tuple => tuple.Item2 != tuple.Item3) returns all those Tuples as an IEnumerable<Tuple<int,int,int>> where Item2 and Item3 are not the same. If the count of such entries is 0 then we can conclude that the game is over.

Have a go hero

Now that you know list-based containers and dictionaries, can you add a recording feature to the number rearrange game. This should enable players to record their moves from the initial board positions. Being able to save and retrieve a saved game would be great. Remember that there can be several players starting from the same board.

Hint: You might want to use this structure to store and replay games for users:

List<Tuple<string, List<Tuple<int, int, int>>, List<string>>> games

The first string in the Tuple will represent the name of the player, the inner Tuple one will represent the game board and the last List of string will represent the moves the player used to solve the puzzle or wherever he/she saved the game last.

Tuples have been around for a long time in functional programming languages. Recently, Microsoft research created a new programming language called F#, which has great support for Tuples. Although it is not related to Generics, it is a great thing to know how C# is borrowing from other languages and growing.

Summary

We learned a lot in this chapter about .NET Generics dictionary-based containers.

Specifically, we covered:

  • Dictionary<TKey,TValue>

  • SortedDictionary<TKey,TValue>

  • KeyValuePair<TKey,TValue>

  • Tuples

  • How to design dictionaries to support one-to-many mapping

We also discussed how to create a custom generic dictionary using the inbuilt generic containers. Now that we've learned about all the lists and dictionary-based containers, we're ready to learn more about how to query them better using LINQ. That's the topic of the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.42.233