BackgroundWorker

Puzzle Solved! Politician Name Swap

Posted on Updated on

The Challenge

The challenge comes from listener Steve Daubenspeck of Fleetwood, Pa. Take the first names of two politicians in the news. Switch the first letters of their names and read the result backward to name something that each of these politicians is not.

Link: http://www.npr.org/2015/04/19/400614542/w-seeking-w-for-compound-word-dates

Screen Shot - I checked my lists of common first names to find any which I could combine to create a word in my dictionary file
Screen Shot showing the solution. – I checked my lists of common first names to find any which I could combine to create a word that exists in my list of all English words. Technically, my code didn’t solve the puzzle, it just generated a relatively short list (51 candidates) for me to check manually.

Techniques to Solve

My algorithm uses the following techniques:

  • Background Workers
  • Linq, Lambda Expressions and String Manipulation
  • Binary Search
  • File IO

Algorithm Overview

I don’t know of any list containing ‘politicians in the news’. I looked around on the web, and the closest thing I could find was a Wikipedia list of lists. It generally takes a quite while to chase down all those sub-lists, but I already had couple of nice lists of common first names (boy names and girl names). I elected to try checking all first names that might match the solution and then see how many names applied to politicians I recognized. Since only a small number (51) names could be manipulated to form an English word, it was relatively easy to look through those 51 and manually check if any matched-up with prominent politicians.

BackgroundWorker – Rationale

I used a BackgroundWorker because I wanted the grid to display work-in progress while it worked, instead of waiting until the end to see the all results. Since it takes over 2 minutes to solve, it’s a nice way to tell the users progress is being made. Important: in order to make this work, your grid should be bound to an ObservableCollection so that your grid will be aware of your updates.

When you use a BackgroundWorker, you hook-it-up to several methods which it will run at the appropriate time, including:

  • DoWork – your method where you do the work!
  • ProgressChanged – an event that is raised, where your other method can update the UI
  • RunWorkerCompleted – fired when the DoWork method completes

The point is that we will update the UI in our ProgressChanged method, which will be on a different thread than the DoWork method. This allows the UI to update almost immediately, instead of waiting for the main thread to finish. Here’s how I set-up my worker:

//Declare the background worker at the class-level
private BackgroundWorker _Worker;
//... Use it inside my button-click event:
_Worker = new BackgroundWorker();
_Worker.WorkerReportsProgress = true;
//Tell it the name of my method, 'Worker_DoWork'
_Worker.DoWork += Worker_DoWork;
//Tell the worker the method to run when it needs to report progress:
_Worker.ProgressChanged += Worker_ProgressChanged;
//Likewise, tell it the name of the method to run when complete:
_Worker.RunWorkerCompleted += Worker_RunWorkerCompleted;

_Worker.RunWorkerAsync();

Hopefully this is pretty clear, my methods are nothing special. If you’re confused, I invite you to download my code (link at the bottom of this post) and take a look, Now let’s talk about the Linq and Lambda expressions I used inside my method ‘Worker_DoWork’.

Linq and Lambda Expressions

I didn’t use Linq/Lambda a lot, but I did use them to work with my list of names, specifically

  1. to perform a ‘self-join’ on my list of names
  2. to reverse my string (which gives me a list of char)
  3. and the ‘Aggregate’ method to convert my list of char back to a string

Here’s what I’m talking about:

var candidates = from n1 in allNames
                 from n2 in allNames
                 where n1 != n2
                 select new SolutionCandidate {
                     Combined = (n2[0] + n1.Substring(1) + n1[0] + n2.Substring(1))
                                                                     .Reverse()
                                                                     .Aggregate("", (p, c) => p + c),
                     Name1 = n1,
                     Name2 = n2
                 };

What is this code doing? It takes every entry in allNames, in combination with a corresponding entry in another instance of allNames (in case you’re wondering, ‘allNames’ is just a list of first names). The names in the first instance are referred to using the variable name ‘n1’, and the names in the second instance are referred to using name ‘n2’. In other words, create every possible 2-name combination. This is the equivalent of a Cartesian Product, which you should normally avoid due to huge results sets. But in this case, we actually want a Cartesian Product. If you’re thinking algorithm analysis, this code runs in O(n2) time. (Link explains my notation: http://en.wikipedia.org/wiki/Big_O_notation)

For every name pair (and there are quite a few!), the code above creates a SolutionCandidate, which is a little class with three properties, namely, ‘Combined‘, ‘Name1‘ and ‘Name2‘.

Take a look at how I create the ‘Combined’ property:

  • n2[0]   – this means: take the 0th char from n2 (i.e., the name from the 2nd instance of allNames)
  • + n1.Substring(1)  this means take a substrig of n1, starting at index 1, up to the end. In other words, all of it except the first letter (at index 0), and concatenate it the previous term (the plus sign means concatenate)
  • + n1[0]  this means get the 0th char of the name from the 1st instance of allNames, concatenate it too
  • + n2.Substring(1)  this means get all of the 2nd name except the first letter, and concatenate

After I do that, I invoke the ‘Reverse’ method on the result. Reverse normally works with lists (actually, with IEnumerable objects), but a string is actually a list of letters (char) so it will revers a string too. The problem is that the result is a list of char.

Finally, I convert my list of char back to a string using the ‘Aggregate‘ method. If I performed Aggregate on an list of numbers, I could use it to add each entry to create a sum, for example (I can do anything I want with each entry). But for a list of char, the equivalent to addition is concatenation.

Binary Searching

Binary search is an efficient way to check if some candidate is in your list. In our case, I want to check my list of all words to see if I created a real word by manipulating two first names. Here’s how I do that, remember that I build my list ‘candidates’ above and it contains a bunch of potential solutions:

foreach (var candidate in candidates)
    if (allWords.BinarySearch(candidate.Combined) >= 0) {
        //pass the candidate to a method on another thread to add to the grid; 
        //because it is on another theread, it loads immediately instead of when the work is done
        _Worker.ReportProgress(0, candidate);
    }

When I invoke ‘BinarySearch’, it returns the index of the entry in the list. But if the candidate is not found, it returns a negative number. BinarySearch runs in O(Log(n)) time, which is good enough for this app, which really only runs once. (I could squeeze out a few seconds by using a Dictionary or other hash-based data structure, but the dictionary would look a bit odd because I only need the keys, not the payload. The few seconds I save in run time is not worth the confusion of using a dictionary in a weird way.)

File IO

OK, I used some simple file IO to load my list of names, and also my list of all words. The code is pretty straightforward, so I’ll just list it here without any exegesis.

List//allNames will hold boy and girl names
//I run this code inside 'Worker_DoWork'
ReadNameFile(ref allNames, BOY_NAME_FILE, allWords);
ReadNameFile(ref allNames, GIRL_NAME_FILE, allWords);

//Here's the method that loads a file and adds the name to the list
private void ReadNameFile(ref List allNames, string fName, List allWords) {
    string[] wordsArray = allWords.ToArray();
    using (StreamReader sr = File.OpenText(fName)) {
        while (sr.Peek() != -1) {
            //The name is columns 1-16, so trim trailing blanks
            string aName = sr.ReadLine().Substring(0, 15).TrimEnd();
            int p = allNames.BinarySearch(aName);
            if (p < 0)
                //By inverting the result form BinarySearch, we get the position
                //where the entry would have been. Result: we build a sorted list
                allNames.Insert(~p, aName);
        }
    }
}

Notes on Improving the Efficiency

We pretty much need to perform a self-join on the list of names, and that is by far the slowest part of the app. It would be faster if we could reduce the size of the name list. One way to do that is to reject any names that could never form a solution. For example, ‘Hillary’ could not be part of a solution because,

  • when we remove the first letter (H),
  • we are left with hillar,
  • which, when reversed, becomes ‘rallih’
  • Since there are no English words that start with, or end with, ‘rallih’, I know that ‘Hillary’ is never part of the solution

I briefly experimented with this method; there are some issues. First, the standard implementation of BinarySearch only finds exact matches, not partial hits. Same thing for hash-based data structures, and a linear search is way too slow. You can write your own BinarySearch that will work with partial hits, but that leads us to the second issue.

The second issue is that, in order to do an efficient search for words that end with a candidate string, you need to make a separate copy of the word list, but with all the words spelled backwards, and run your custom binary search on it, again modifying the search to find partial matches.

After starting down that path, I elected to call it a wrap. My app runs in 2 minutes, which is slow but still faster than re-writing my code, perhaps to save 30 seconds of run time. To any ambitious readers, if you can improve my algorithm, please leave a comment!

Summary

I build a list of common names (with some simple file IO) and matched it against itself to form all combinations of common names (using Linq/Lambda Expressions); for each pair, I manipulated the names by swapping first letters and concatenating them. I then checked that candidate against my master list of all words, using a BinarySearch to check if the candidate exists in that list..

Download your copy of my code here

NPR Puzzle 12-Dec-2013 – City Name to Literary Family

Posted on Updated on

Regular readers know that I love to solve the NPR Sunday puzzle in code and share my techniques on my blog; here’s the latest puzzle solved in code! This was a bit of a fun one because I got to use a  BackgroundWorker and a progress bar, as a bonus, solving the puzzle was easy after I downloaded the correct data.

The challenge:

Name a U.S. city in nine letters. Shift the third letter six places later in the alphabet. Then shift the last letter seven places later in the alphabet. The result will be a family name featured in the title of a famous work of fiction. What is the city, and what is the family name?

 

Screen-shot showing the solution to the puzzle
Screen-shot showing the solution to the puzzle

Visiting 2,000 Pages Using QueryString Parameters!

The tricky part about this one was getting all the book names; I downloaded it from this site http://www.goodreads.com/shelf/show/fiction, which spreads its book list across 2,000 pages. I can handle it by passing a numeric QueryString parameter to the site and get successive pages. Like

"http://www.goodreads.com/shelf/show/fiction?page=1052"

By visiting 2,000 of their pages, I was able to build a list just short of 10,000 book titles.

Even using a fast internet connection (which I don’t have), fetching 2,000 pages is pretty slow, so I used a progress bar and cancel button; that way it maybe seemed faster!

A progress bar allows me to be patient while my app visits a ton of pages; the cancel button makes it easy to abort
A progress bar allows me to be patient while my app visits a ton of pages; the cancel button makes it easy to abort. In order to do both, we need perform the work on a different thread than the UI.

 

Fetching Pages inside a Loop

Actually, visiting 2,000 pages via query strings is easy; all you do is create the correct URL as a string, in code, with a page number, and pass it to a WebClient instance. (Needless to say, this only works if the site in question actually accepts querystrings!)

//The {0} below is a placeholder for the page number; we it use further down
string pageUrl = "http://www.goodreads.com/shelf/show/fiction?page={0}";
using (WebClient wc = new WebClient()) {
	//One pass through the loop for each page
	for (int pageNum = 1; pageNum <= 2000; pageNum++) {
		//Update the URL with the page number:
		//The placeholder receives the integer page number
		string url = string.Format(pageUrl, pageNum);
		//Now go out to the web and fetch the page using the page number:
		using (StreamReader sr = new StreamReader(wc.OpenRead(url))) {
    		    string allText = sr.ReadToEnd();
		...
		...

Use A Background Worker so Your Progress Bar will Update

One minor frustration about WPF is that (naively implemented) progress bars only update the screen after your code stops running, making them seem useless ☹. You can get around this issue by doing your work (fetching book titles) on a different thread than the thread that updates your UI ☺. The easiest way to accomplish this is to use the Microsoft BackgroundWorker class, which raises an event ‘ProgressChanged‘ which you catch on the same thread as your UI, thus circumventing the problem.

When you catch the ProgressChanged event, you can snag data from the event parameters that describes the current progress: “e.ProgressPercentage.” The event arguments also allow you to pass your own custom data in the UserData member of the event args.

void BookFetcher_ProgressChanged(object sender, ProgressChangedEventArgs e) {
        //Sample code shows the event parameter being used to set the progress bar value
	prg.Value = e.ProgressPercentage;
        //The user state holds any object, in this case it is the page number:
	tbPageNum.Text = string.Format("Page {0} of 2000", (int)e.UserState);
}

Setting-Up Your Background Worker

I wanted to show you the end-purpose of using a BackgroundWorker, so I jumped straight to the code above. If you felt that I skipped over the set-up work, you’re right! So now I’ll back-track a bit to explain.

You should declare a background worker at the class-level, and initialize it before you ask it to do any work. In this case, I initialized mine in the button click-event:

using System.ComponentModel;                //For background worker
...
public partial class MainWindow : Window {
...
    private BackgroundWorker _BookFetcher;
....
	private void btnGetFamousBooks_Click(object sender, RoutedEventArgs e) {
                //Initialize my background worker
		_BookFetcher = new BackgroundWorker();

Hooking-Up the BackgroundWorker Events

Since you want your background worker to report progress, you need to link-up with your own event. Similarly, you need to link-up with the DoWork event and the RunWorkerCompleted events, plus tell your worker that it should report progress:

//The background worker has 3 events we care about, link them up:
_BookFetcher.DoWork += BookFetcher_DoWork;
_BookFetcher.ProgressChanged += BookFetcher_ProgressChanged;
_BookFetcher.RunWorkerCompleted += BookFetcher_RunWorkerCompleted;
_BookFetcher.WorkerReportsProgress = true;
_BookFetcher.WorkerSupportsCancellation = true;

Naturally, each of the event names linked-up here correspond to actually event methods; if you are not familiar with this in C#, just try typing the code above; when you reach “+=”, Visual Studio will suggest a method stub for you; you can accept its suggestion by hitting the tab key, and it will write a bunch of code for you, saving you time.

Screen-shot shows Visual Studio about to generate code for me!
Screen-shot shows Visual Studio about to generate code for me!

In order to start our background worker, we need to invoke its RunWorkerAsync method, which does all the work of starting a thread. You can inspect my code listing (at the end of the article) to see what my worker did  in it’s ‘DoWork’ event; basically it is the same kind of screen-scraping, via RegularExpressions, that I explained in several previous posts, including this one.

Raising the ProgressChanged Event with the Correct Parameters

Finally, in order to report progress, we need to raise the event ProgressChanged, like the following:

//Update the progress bar, which is on a different thread:
_BookFetcher.ReportProgress((int)(pageNum * 100 / 2000), pageNum);

Note that the first parameter above (percentage complete) should be an integer between 0 and 100, while the second parameter is an object;  I just happened to pass an integer in its place (since an integer, like every other variable, is an object).

Getting a List of Place Names

OK, I did all that work to get a big list of books, you might be wondering where I got the list of places from. Normally, I would get a list off of Wikipedia, but they only list the top 250  cities, and the solution is not in that list! Fortunately, you can get a list of place names from the census bureau. That certainly makes it easier!

Solving the puzzle

Once we have a list of books and a list of cities, solving the puzzle is pretty easy. (I was prepared to compare solution candidates against a surname list I downloaded from the Census Bureau, but that was not necessary – only one word in 10,000 book titles maps to a city name using the method prescribed in the puzzle above ☺. Solving the puzzle involves

  1. Iterating the city list
  2. Examining only cities with length 9, per puzzle instructions
  3. Manipulating the names as described in the challenge
  4. And comparing result against a dictionary of words found in the book list
    • I used a dictionary because they are extremely fast (hashing technology) and because they are easy to use

In case you aren’t familiar with one additional  technique, you can “shift” letters in the alphabet by retrieving them by position them in your string, and using addition on them, like this:

string cityName = "kalamazoo";
//Using [] square bracket (indexing) on a string returns a char
char shiftedLetter = (char)(cityName[2] + 6);

A char is a strange data type, because addition works on it like an integer, yet you can take the result and append it to a string! Strange because, when you use the + operator on strings, it performs a concatenation operation, not arithmetic addition, but char data behaves like a number instead.

Here is the complete code-listing to solve the puzzle:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Net;                           //For WebClient
using System.Text.RegularExpressions;       //For text matching
using System.IO;                            //To create/read files etc.
using System.ComponentModel;                //For background worker

namespace Dec8_2013
{
    /// <summary>
    /// Code to odwnload the booklist and solve the puzzle
    /// </summary>
    public partial class MainWindow : Window
    {
        private const string BOOK_FILE = "BookList.txt";
        private BackgroundWorker _BookFetcher;

        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Fires when user clicks the button to get the book list
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param><remarks>
        /// Initialize a background worker and start it up.
        /// The web site with book titles has 2000 pages, I used a 
        /// background worker because that allows me to display
        /// a progress bar and cancel button.
        /// </remarks>
        private void btnGetFamousBooks_Click(object sender, RoutedEventArgs e) {
            _BookFetcher = new BackgroundWorker();
            //The background worker has 3 events we care about, link them up:
            _BookFetcher.DoWork += BookFetcher_DoWork;
            _BookFetcher.ProgressChanged += BookFetcher_ProgressChanged;
            _BookFetcher.RunWorkerCompleted += BookFetcher_RunWorkerCompleted;
            _BookFetcher.WorkerReportsProgress = true;
            _BookFetcher.WorkerSupportsCancellation = true;

            //Show the progress bar and cancel buttons:
            prg.Visibility = System.Windows.Visibility.Visible;
            prg.Value = 0;
            btnCancelBookFetch.Visibility = System.Windows.Visibility.Visible;

            //Launch!
            _BookFetcher.RunWorkerAsync();
        }

        /// <summary>
        /// Asynchronously downloads the book list from 2000 pages on one site
        /// </summary>
        /// <param name="sender">The background worker</param>
        /// <param name="e">Event args, which we don't need this time</param>
        /// <remarks>
        /// The site with the titles has 2000 pages, we can access each by 
        /// using the page number as a 'query parameter', such as 'page=335'
        /// 
        /// Use a loop to access each page in succession and extract the titles
        /// using the regular expression, saving to file.
        /// </remarks>
        void BookFetcher_DoWork(object sender, DoWorkEventArgs e) {
            string pageUrl = "http://www.goodreads.com/shelf/show/fiction?page={0}";
            string pattern = @"
                    class=""bookTitle"">        #Literal match
                    ([^(]+)                     #Capture group - anything except (, one or more
                    \(                          #Escaped close-parenthesis
                    ";

            Regex reBook = new Regex(pattern,
                RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
            using (StreamWriter sw = File.CreateText(BOOK_FILE)) {
                using (WebClient wc = new WebClient()) {
                    for (int pageNum = 1; pageNum <= 2000; pageNum++) {
                        //Update the URL with the page number:
                        string url = string.Format(pageUrl, pageNum);
                        using (StreamReader sr = new StreamReader(wc.OpenRead(url))) {
                            string allText = sr.ReadToEnd();

                            Match m = reBook.Match(allText);
                            while (m.Success) {
                                sw.WriteLine(m.Groups[1].Value.TrimEnd());
                                m = m.NextMatch();
                            }

                            //Update the progress bar, which is on a different thread:
                            _BookFetcher.ReportProgress((int)(pageNum * 100 / 2000), pageNum);

                            if (_BookFetcher.CancellationPending)
                                break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Fires when the background worker has progress to report
        /// </summary>
        /// <param name="sender">The background worker</param>
        /// <param name="e">Event args, including ProgressPercentage and UserState</param>
        void BookFetcher_ProgressChanged(object sender, ProgressChangedEventArgs e) {
            prg.Value = e.ProgressPercentage;
            tbPageNum.Text = string.Format("Page {0} of 2000", (int)e.UserState);
        }

        /// <summary>
        /// Fires when the background worker finishes
        /// </summary>
        /// <param name="sender">The background worker</param>
        /// <param name="e">Event args</param>
        void BookFetcher_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            prg.Visibility = System.Windows.Visibility.Hidden;
            MessageBox.Show("Books Captured", "Mission Accomplished",
                MessageBoxButton.OK, MessageBoxImage.Exclamation);
        }

        /// <summary>
        /// Cancels the book title fetch
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCancelBookFetch_Click(object sender, RoutedEventArgs e) {
            _BookFetcher.CancelAsync();
        }

        /// <summary>
        /// Cleans-up some abnormally formatted book titles and puts the results in a new file
        /// </summary>
        /// <param name="sender">Tht button</param>
        /// <param name="e">Event args</param>
        private void btnCleanUp_Click(object sender, RoutedEventArgs e) {
            string allText;
            using (StreamReader sr = File.OpenText(BOOK_FILE)) {
                allText = sr.ReadToEnd();
            }

            string pattern = @"</a>[\n\r<>\w\s/'=:.""?-]*?Text"">";
            string fixedUp = Regex.Replace(allText, pattern, "");

            using (StreamWriter sw = File.CreateText("CleanedBookList.txt")) {
                sw.WriteLine(fixedUp);
            }
        }

        /// <summary>
        /// Does the work to solve the puzzle
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        /// <remarks>
        /// Build a dictionary of every 9-letter name in the book titles, using the
        /// word (a potential last-name) as the key and the book title as the value.
        /// 
        /// Then, process the book file, manipulating the 3rd and 9th characters
        /// as described in the challenge, to form a candidate solution. If the
        /// solution exists in our book dictionary, we have a winner!
        /// </remarks>
        private void btnSolve_Click(object sender, RoutedEventArgs e) {
            //instantiate and populate the book dictionary:
            Dictionary<string, string> bookDic = new Dictionary<string, string>();
            using (StreamReader sr = File.OpenText("CleanedBookList.txt")) {
                while (sr.Peek() != -1) {
                    string aLine = sr.ReadLine();
                    string[] words = aLine.ToLower().Split(' ');

                    //put every 9-letter word into the dictionary using
                    //the title as the value and the word as the key
                    foreach (string aWord in words) {
                        if (aWord.Length == 9) {
                            if (bookDic.ContainsKey(aWord) && bookDic[aWord] != aLine) {
                                //some words are used in multiple titles
                                //if so, concatenate them
                                bookDic[aWord] += ";" + aLine;
                            } else if (!bookDic.ContainsKey(aWord)) {
                                bookDic.Add(aWord, aLine);
                            }
                        }
                    }
                }
            }

            //Now process the cities file, which I downloaded from a
            //government web site
            string answers = "";
            string fName = "AmericanPlaces2k.txt";
            using (StreamReader sr = File.OpenText(fName)) {
                while (sr.Peek() != -1) {
                    //The file format has city name in colunmns 10-75,
                    //we only need 15 characters because we know the city
                    //name should be 9 chracters, we also need to
                    //capture the type 'cit', 'town', 'CDP', municipality
                    string aLine = sr.ReadLine().Substring(9, 15).ToLower();
                    //look for the first blank, which tells us the name len
                    int p = aLine.IndexOf(' ');
                    if (p == 9) { 
                        //ignore the line unless the type is correct
                        string cityType = aLine.Substring(10, 4);
                        if (cityType == "town" || cityType == "city" ||
                            cityType == "CDP " || cityType == "muni") {
                            string cityName = aLine.Substring(0, 9);
                            //shift the 3rd letter 6 places, then shift
                            //the last letter 7 places
                            if (cityName[2] <= 'z' - 6
                                && cityName[8] <= 'z' - 7) {
                                string candidate = cityName.Substring(0, 2) //1st 2 chars
                                    + (char)(cityName[2] + 6)               //Shift letter 3 by 6
                                    + cityName.Substring(3, 5)              //Copy next 5 chars
                                    + (char)(cityName[8] + 7);              //Shift last by 7
                                if (bookDic.ContainsKey(candidate)) {
                                    answers += cityName + " - " + bookDic[candidate] + "\n";
                                }
                            }
                        }
                    }
                }
            }

            txtAnswer.Text = answers;
        }
    }
}

Here is the XAML:

<Window x:Class="Dec8_2013.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        WindowStartupLocation="CenterScreen"
        Title="City Name to Ficticious Family Name" Height="450" Width="625">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Border Grid.ColumnSpan="5">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="Wheat" Offset="0" />
                    <GradientStop Color="SlateBlue" Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <TextBlock HorizontalAlignment="Center" FontSize="32" Foreground="AliceBlue" 
                        Text="City Name to Fictional Family Name">
                <TextBlock.Effect>
                    <DropShadowEffect />
                </TextBlock.Effect>
            </TextBlock>
        </Border>

        <TextBlock Grid.Row="1" Grid.ColumnSpan="5" FontSize="13" 
                   TextWrapping="Wrap" Margin="3">
            <Bold>Next week's challenge from listener Pete Collins of Ann Arbor, Mich.:</Bold> 
            Name a U.S. city in nine letters. Shift the third letter six places 
            later in the alphabet. Then shift the last letter seven places 
            later in the alphabet. The result will be a family name featured 
            in the title of a famous work of fiction. What is the city, 
            and what is the family name?
        </TextBlock>

        <Button Grid.Row="2" Content="Get Famous _Books" x:Name="btnGetFamousBooks" Height="30" Margin="3"
                HorizontalAlignment="Left" Click="btnGetFamousBooks_Click" />

        <!-- The progress bar is invisible until user clicks the button to start-->
        <ProgressBar Grid.Row="2" Grid.Column="1" Height="20"
                     Width="200" x:Name="prg"
                     Foreground="DarkBlue" Background="Chartreuse" Visibility="Hidden"
                     Minimum="0" Maximum="100" />
        <TextBlock Grid.Row="2" Grid.Column="2" x:Name="tbPageNum" VerticalAlignment="Center" />
        <Button Grid.Row="2" Grid.Column="3" Content="_Cancel Fetch" 
                Height="30" Margin="3" x:Name="btnCancelBookFetch"
                Visibility="Hidden"
                Click="btnCancelBookFetch_Click" />

        <Button Grid.Row="3" Content="_Clean-Up Books" Height="30" Margin="3"
                x:Name="btnCleanUp" Click="btnCleanUp_Click"  />

        <Label Grid.Row="4" Content="_Answer" Target="{Binding ElementName=txtAnswer}" />
        <TextBox Grid.Row="4" Grid.Column="1" x:Name="txtAnswer"                
                 AcceptsReturn="True" TextWrapping="Wrap"  />
        <Button Grid.Row="4" Grid.Column="2" Content="_Solve" Height="30"
                Margin="3" x:Name="btnSolve" VerticalAlignment="Top"
                Click="btnSolve_Click" />
    </Grid>
</Window>

Tuples for Convenience and Speed

Posted on Updated on

Being a smart programmer means knowing when to use the right tool for the job at hand, and when not to use those same tools! In this post I try to be smart about using the Tuple class, illustrated by an example. Possibly you have already encountered Tuples and were wondering when you should use them; I hope my simple example will provide you with some ideas.

My Problem

I want to use a BackgroundWorker and I need to pass it two arguments (for good reasons which divert from my main point, so won’t be explained here). BackgroundWorker has a method ‘DoWork’, but it only accepts one input arugment!

Here are three alternatives we could use to address the problem:

  • We could combine our arguments to a single string, thus creating a single
    argument. I prefer not because strings are not strongly typed, and
    converting back and forth is work I would like to avoid if possible.
  • We could make a single-use class, create an instance of it, and pass it
    to our DoWork method. Again, more work than I wish to perform.
  • Or, we could use a Tuple to pack our arguments into a single object, as illustrated in the sample below:

        //Pack my variables into a single variable
        var args = Tuple.Create(myTotal, myBoolArg);
        //Kick-off the worker
        _FetchWorker.RunWorkerAsync(args);        

        ...
        //Inside the 'DoWork' method, we need to extract 
        //our arguments from e.Argument
        void FetchWorker_DoWork(object sender, DoWorkEventArgs e) {

            //Since e.Argument's type is 'object', we need to cast 
            //it back to the original type
            Tuple<int, bool> args = (Tuple<int, bool>)e.Argument;
            //Put the arguments into nicely named variables:
            int myTotal = args.Item1;
            bool myBoolArg = args.Item2;
        }
        

A Couple of Explanatory Notes

Every tuple has properties Item1… Item8, that is, up to 8 items. Every property is strongly-typed, according to this principle: if you put an int in, your tuple gets an int property, if you put a bool in, you get a bool out. The correct property type is created for you, automatically. Whatever type you put in, you get that same type out.

And when I say ‘put it in‘, I mean use the ‘Tuple.Create method’. As many things as you put in (up to 8), that is how many you get out. Simple!

Usage (Abusage?)

Tuples are great for single-use classes. Here is my proposed usage principal: if you use same your tuple in more than two places, then do the work and create a dedicated class instead. Why? Because myClass.Total is more readable than myClass.Item1. ‘Item1’ is generic and requires you to backtrack to understand the purpose, but ‘Total’ is self-explanatory.

This simple sample is just the start, you can use Tuples in many places where you need a quick-and-dirty way to lump data together into a temporary object. Have fun!