Month: February 2015

NPR Puzzle Solved!

Posted on

What a blast to solve this one! Once in a while, everything works exactly right, and this was one of those fabulous times. My program ran the first time! (Which only happens a few times a year), plus I had all the right files on hand, and I didn’t need to fuss with any bad data. Bottom line: it took way longer to write this blog post than it took to solve the puzzle.

The challenge:

This week’s challenge comes from listener Smatt Read of Somerville, Mass. Actor Tom Arnold goes by two first names — Tom and Arnold, both male, of course. And actress Grace Kelly went by two first names — Grace and Kelly, both female. Name a famous living actress who goes by three first names, all of them traditionally considered male. The names are 5, 3 and 6 letters long, respectively.

The Solution

Screen shot showing the solution
Screen shot showing the solution

Techniques You Will Learn

  • Simple File IO
  • String manipulation
  • Binary search and generic list manipulation

The Code

private void btnSolve_Click(object sender, RoutedEventArgs e) {
    Mouse.OverrideCursor = Cursors.Wait;
    txtSolution.Clear();

    //Load the list of boy names into a sorted list
    List<string> boyNames = new List<string>();
    using (StreamReader sr = File.OpenText(BOY_NAME_FILE)) {
        //Read every line in the file
        while (sr.Peek() != -1) {
            string aLine = sr.ReadLine();
            //The name is the first thing on the line, and it is immediately followed by a space
            int p = aLine.IndexOf(' ');
            //Grab the name out of the line we just read:
            string aName = aLine.Substring(0, p);
            p = boyNames.BinarySearch(aName);
            if (p < 0) {
                //Binary search returns a negative number if not found, by inverting
                //the number, we get the index where the name belongs in the sort order
                boyNames.Insert(~p, aName);
            }
        }
    }

    //This folder contains a number of files, for with names 
    //like, for example, 'British_silent_film_actresses.txt'
    string[] actressFiles = Directory.GetFiles(ACTRESS_FOLDER);
    foreach (string fName in actressFiles.Where(f => f.IndexOf("actress") >= 0)) {
        //Open all the actress files and check each against the boy name list
        using (StreamReader sr = File.OpenText(fName)) {
            while (sr.Peek() != -1) {
                string aLine = sr.ReadLine();

                //build an array with each name filling a slot in that array
                string[] tokens = aLine.ToUpper().Split(' ');
                if (tokens.Length > 2) {
                    int matchCount = 0;
                    //Count how many names, for this actress, are found in the boy list:
                    foreach (string aName in tokens) {
                        int p = boyNames.BinarySearch(aName);
                        //Binary search is very fast and returns a non-negative number if found
                        if (p >= 0)
                            matchCount++;                                
                    }
                    //If we have at least 3 matches, we found a winner!
                    if (matchCount > 2) {
                        txtSolution.Text += aLine + "\n";
                    }
                }
            }
        }
    }
    Mouse.OverrideCursor = null;
}

Data Comments

I originally got the actress files from wikipedia http://en.wikipedia.org/wiki/Lists_of_actresses to solve a different puzzle. I believe I obtained the list of boy names from the census bureau a long time ago, but I can’t find the link any more.

Download the code (includes data files)!

Defensive Coding with Linq and Generic Methods

Posted on Updated on

Suppose you work with some off-shore programmers. Also suppose that those off-shore programmers write your BLL (Business Logic Layer) Code. Further, suppose those programmer constantly change your results without telling you. Finally, suppose they give you results in DataTable format, which is completely flexible and not strongly typed, thus making it hard to detect unannounced changes.

Given those assumptions, you’ve got a situation where they break your code all the time and  leave you to clean-up the mess. And that is the situation I will address in this post.

Not to slam off-shore programmers per se, but perhaps something about working in a different time zone, in a different culture, makes it more likely they will not understand your needs, even if those needs seem blindingly obvious to you! So, my attitude is, just do the best you can under the circumstances and hope the blame falls on the right shoulders. Defensive coding will help. Even if your teammates work in the cube next to you, that type of coding can help.

What You Will Learn

  1. How to use Linq (actually Lambda expressions) on DataTables to check for missing columns
  2. How to use Generic Methods to simplify your logic for extracting data out of a DataTable while checking for misnamed columns

Step One: Check if the Table Returned by the BLL Team has the Columns You Need

If the result set lacks columns we need, we need to log that problem. Also, we should try to work with the data we get and avoid completely crashing. Here is my short-and-sweet method to log missing columns and check how many desired data columns are present in the data we get from the BLL. Basically, I use the ‘Except’ method to compare one list (the expected columns list) against another list (the actual column list in the data table):

public int ColumnMatchCount(DataTable dtReturnTable,
                            List<string> expectedColumnNames,
                            string callingMethod)
{
    //Use the lambda expression 'Except' to determine which columns
    //are not present in the data table provided by BLL
    var unmatched = expectedColumnNames
        .Except(dtReturnTable.Columns.Cast<DataColumn>()
        .Select(c => c.ColumnName));
    foreach (string missingCol in unmatched) {
        //Log the problem column
        _Logger.Log(LogLevel.Error, callingMethod +
                    " - expected BLL to provide column '"
                    + missingCol
                    + "' in the ReturnTable, but it was not present.");
    }
    //Tell the caller how many columns match
    return expectedColumnNames.Count - unmatched.Count();
}

Key point: The data table has a columns collection we can compare using the Except method. Since DataTables are not quite the type of data that works with Linq, we use ‘Cast<DataColumn>() to make ti work. Because DataTables existed before Linq.

In my case, I will give my clients data if at all possible, i.e. if the BLL crew has given me at least one column that matches what they told me they would provide. I’ll provide some sample code shortly, but first, let’s look at my method to get the data out of the data table when the column I expect might not be there.

Step Two: Get Data from the DataTable  When a Column is Missing

If the BLL gave me the column I expected, no big deal (of course, I also want to handle possible null values). But, if the column I want is not even present, a default value should be used instead. The method below does that, and it works for any data type, whether string, int, DateTime, etc. Because it is written as a Generic Method, it can handle any data type you provide.

public T CopyValueFromTable<T>(DataRow dr, string columnName)
{
    T result = default(T);
    if (dr.Table.Columns.Contains(columnName) && ! dr.IsNull(columnName))
        result = (T)dr[columnName];
    return result;
}

The method takes a data row as input and examines that DataRow’s parent table.

    dr.Table

That table has a Columns collection, and we can check it to see if the column exists or not.

    if (dr.Table.Columns.Contains(columnName)

If the BLL crew remembered to give us the column we need, and it it is actually present and not null, then extract it.

dr[columnName]

If you understand Generic Methods, you will recognize that ‘T’ is the data type our caller wants us to use, so we convert the data column to type ‘T’. We cast it to type T using the parenthesis notation; remember that
‘T’ is a placeholder for any type, such as int, string, DateTime, etc.

    result = (T)dr[columnName];

The first line in my method ensures that an appropriate default value will be returned if no other choice is available, the ‘default’ method will give us an empty string, a zero, etc., depending on what type is represented by ‘T’:

    T result = default(T);

Putting it All Together

Screen shot shows my code handling unexpected missing column 'ServiceCenterID' and using a default value instead. The discrepancy was logged.
Screen shot shows my code handling unexpected missing column ‘ServiceCenterID’ and using a default value instead. The discrepancy was logged.
private void btnSimulateUnplanedChange_Click(object sender, RoutedEventArgs e) {
    BLL dataFetcher = new BLL();
    //Ask our simulated BLL to get some data, which will lack the column 'ServiceCenterID'
    DataTable ReturnTable = dataFetcher.UnannouncedChange_GetServiceCenters();
    List<string> expectedColumns = new List<string> { "Location", 
                                "LocationDescription", "ServiceCenter", "SCDescription" };
    List<InventoryResponse> response = new List<InventoryResponse>();


    //If at least one column name matckes, 
    if (ColumnMatchCount(ReturnTable, expectedColumns, "GetResults") > 1) {
        foreach (DataRow dr in ReturnTable.Rows) {
            InventoryResponse invResp = new InventoryResponse();
            invResp.ServiceCenterID = CopyValueFromTable<int>(dr, "ServiceCenterID");
            invResp.Location = CopyValueFromTable<string>(dr, "Location");
            invResp.SCDescription = CopyValueFromTable<string>(dr, "SCDescription");
            response.Add(invResp);
        }
        txtResults.Text = RjbUtilities.DebuggerUtil.ComplexObjectToString(response);
    } else {
        _Logger.Log(LogLevel.Error, "No column names match the expected values");
    }
}

Summary

Sometimes you can’t work with top-quality programmers and possibly they don’t understand the concept of their teammates depending on their code. If you can’t force them to grow-up and communicate, at least you can make sure your code logs the problem and does the best it can with what they give you.

I showed you one method ‘ColumnMatchCount’ that checks how many expected columns are present the results the BLL crew gives you. I showed you another method ‘CopyValueFromTable’ that will provide default values and avoid crashing if the BLL crew renamed a column without telling you.

If you write similar defensive code, you might be lucky and your managers will blame the appropriate people!

Download my sample code here.