Linq

Calculator Letters Puzzle – Solved!

Posted on Updated on

Or, Fun with Lambdas and Set Functions!

The challenge:

This three-part challenge comes from listener Lou Gottlieb.
If you punch 0-1-4-0 into a calculator, and turn it upside-down, you get the state OHIO.
What numbers can you punch in a calculator, and turn upside-down, to get a state capital,
a country and a country’s capital?

Upside-down Calculator
Upside-down calculator letters are the alphabet we can use to solve this puzzle

Puzzle Location:
http://www.npr.org/2014/09/14/348220755/the-puzzle-and-the-pea

The Solution Revealed!

Screen Shot of Solution
Screen shot shows the state capital, country and world capital, all of which can be spelled using ‘Calculator Letters’
The first thing we need is a list of countries, state capitals, and world capitals. Fortunately, I already have files containing the required data (you can get those files if you download the solution code below):
  • State Capitals
  • World Capitals
  • Countries

I’ve included those data files in my code download; if you run my code, note that their path will be different on your PC than mine.

Next, we’ll make a list of the available letters, which I have listed in the image below:

Revised Calculator Letters
Image shows how we start with the calculator numbers, rotate them 180 degrees, them map them to letters. IN the case of 4, it could be either h or n.

Once we have the data, it is basically a matter of determining whether one string set is entirely included within another string set. In other words, we will ask, for a number of solution candidates, whether it is a proper subset of the set of calculator letters.

To determine whether we have a subset, it is fun to use the Linq built-in set operators. In particular, we can use the method ‘Except‘. I also could have used the HashSet functionality, which actually has a method named ‘IsProperSubSet’, but HashSet is not as convenient as Linq and I just wanted to crank-out some code!

Using Linq, we can determine that one string is a proper subset of another if, when we use Except on them, the result is the empty set, i.e. a list with no members. Examples:

  • “atlanta”.Except(“beignlos”) will return a list containing (‘a’, ‘t’), i.e. not a subset.
  • “cheyenne”.Except(“beignlos”) will return a list containing (‘c’, ‘h’, ‘y’). Also not a subset.
  • “boise”.Except(“beignlos”) returns the empty set, i.e. it is the answer!

Note: “beignlos” is a string containing all the calculator letters. The true benefit of the ‘Except’ method is apparent below:

Here’s a Super-Cool Sample of the .NET Set-Operator ‘Except’

var leftoverLetters = "boise".Except("beignlos");
if (leftoverLetters.Count() == 0)
	Console.WriteLine("Ding ding ding - we have a winner!");

Conclusion: ‘boise’ is a subset of ‘beignlos’ –> you can spell ‘Boise’ using upside down calculator letters, and the Except  function tells us that.

Use Set-Operators at Work, Too!

At work, I have recently used the ‘Except’ operator to find orders that were lost. Something along these lines: ‘var missing = outboundOrders.Except(inBoundOrders);

My guess is you can use the Except operator to identify a lot of other things!.

Using Lambdas as ‘First Class’ Objects

First, a little bit of terminiology: “First class objects” are objects that you can use like any other entity. Such uses include passing them as parameters. When you pass a Lambda expression as a parameter, you are taking advantage of the fact that Lambdas are first class objects.

Why did I tell you that? Because my code benefited from it in this fun little app. In particular, I have these needs:

  • I need to perform the same work on 3 different files (state capitals, world capitals, countries)
  • Each file has a different layout
  • I would like to write a single method to do the same work for each file
  • To do so, I could write 3 different lambdas; each knowing how to extract the candidate from a line in a file
  • And then pass each lambda as an input parameter it to my method
  • Benefit: I write one method, which uses a lambda to extract the candidate, instead of 3 methods, each with their own
    techinque to extract the candidates

Here’s the Code

private void btnSolve_Click(object sender, RoutedEventArgs e) {
  //Make a definition list of the calculator letters.
  //This maps letters to the numbers they represent in a calculator. 
  //Each letter is in lower case to make matching easier
  //Note: a 'tuple' is a type of anonymous class
  List<Tuple<char, int>> calculatorLetterMap = 
  new List<Tuple<char, int>>() { Tuple.Create('b', 8), 
    Tuple.Create('e', 3), 
    Tuple.Create('i', 1), 
    Tuple.Create('g', 6), 
    Tuple.Create('n', 4), 
    Tuple.Create('l', 7), 
    Tuple.Create('o', 0), 
    Tuple.Create('s', 5) 
  };

  //The next 3 lines process state capitals:
  Func<string, string> extractStatCap = (aLine => { 
      //Look for hyphen, take the rest of the line, trimmed, in lowercase
      return aLine.Substring(aLine.IndexOf('-') + 2)
      .TrimEnd()
      .ToLower(); 
      });
  List<string> stateCapHits = GetFileMatchesAgainstLetterSet(STATE_CAPITALS_FILE, 
                              calculatorLetterMap, extractStatCap);
  DisplayHits(stateCapHits, "State Capitals", calculatorLetterMap, txtResults);

			
  //Now world capitals:
  //The function is a bit more complicated because some names have spaces, such as 'Abu Dhabi'
  Func<string, string> extractPlaceFunc = (aLine => { 
    //Look for a tilde and take everything prior to it, removeing non-letters
    return aLine.Substring(0, aLine.IndexOf('~'))
          .Where(c => char.IsLetter(c))
          .Aggregate("", (p,c) => p + c); 
    });
    List<string> worldCapHits = GetFileMatchesAgainstLetterSet(WORLD_CAPITALS_FILE, 
              calculatorLetterMap, extractPlaceFunc);
    DisplayHits(worldCapHits, "World Capitals", calculatorLetterMap, txtResults);

			
    //Finally, country names:
    Func<string, string> extractCountryFunc = (aLine => aLine);
    List<string> countryHits = GetFileMatchesAgainstLetterSet(COUNTRIES_FILE, 
          calculatorLetterMap, extractCountryFunc);
    DisplayHits(countryHits, "Countries", calculatorLetterMap, txtResults);
  }

  /// <summary>
  /// Loops through a file and returns a list of every entry which
  /// can be composed solely with 'calculator letters'
  /// </summary>
  /// <param name="fName">The file containing things that might match</param>
  /// <param name="calculatorLetterMap">List of letters derived from upside-down 
  /// calculator numbers</param>
  /// <param name="extractPlaceFunc">Function to extract the item of interest 
  /// from each line in the file</param>
  /// <returns></returns>
  private static List<string> GetFileMatchesAgainstLetterSet(string fName, 
          List<Tuple<char, int>> calculatorLetterMap, 
          Func<string, string> extractPlaceFunc) {
  List<string> result = new List<string>();
  using (StreamReader sr = File.OpenText(fName)) {
    while (sr.Peek() != -1) {
      //Execute the Lambda provided by our caller:
      string candidate = extractPlaceFunc(sr.ReadLine());
      var leftOverLetters = candidate
        .ToLower()
        .Except(calculatorLetterMap.Select(m => m.Item1));
        //If there are no leftover letters, we have a winner!
        if (leftOverLetters.Count() == 0) {
        string propperCase = char.ToUpper(candidate[0]) + candidate.Substring(1);
        result.Add(propperCase);
      }
    }
  }

  return result;
}

  /// <summary>
  /// Displays every hit in the list with its equivalent in 'calculator letters'
  /// </summary>
  /// <param name="hitList">List if items to dispaly</param>
  /// <param name="caption">Caption, such as 'State Capitals'</param>
  /// <param name="calculatorLetterMap">List of letter/number pairs for translating</param>
  /// <param name="txtResult">Textbox to hold the result</param>
  private static void DisplayHits(List<string> hitList, string caption, 
        List<Tuple<char, int>> calculatorLetterMap, TextBox txtResult) {
    txtResult.Text += caption + "\n";
    foreach (string aHit in hitList) {
      string number = MapLettersToCalculatorNumbers(aHit, calculatorLetterMap);
      txtResult.Text += string.Format("\t{0} = {1}\n", number, aHit);
    }
  }

  /// <summary>
  /// Translates the puzzle answer ('candidate') back into 'Calculator letters'
  /// </summary>
  /// <param name="candidate">Such as 'boise' or 'benin' or 'lisbon'</param>
  /// <param name="calculatorLetterMap">List of letter/number pairs</param>
  /// <returns>A string composed of numerals</returns>
  private static string MapLettersToCalculatorNumbers(string candidate, 
    List<Tuple<char, int>> calculatorLetterMap) {
    string result = (from c in candidate
                     join m in calculatorLetterMap on c equals m.Item1
                     select m.Item2
                     ).Aggregate("", (p, c) => p + c);
    return result;
  }

Download the code here: Puzzle Code

ILookup: A Hidden, Powerful Feature of Linq

Posted on Updated on

The ILookup Interface Just Made My Life Easier!

It was a classic case of something that sounded easy to the users, but which was, in reality,  somewhat tricky. All right, I admit it, at first I thought would be easy too!

Problem: our ERP sometimes generates two line items for the same product. Actually, it wasn’t a problem until we got a new eCommerce partner and they griped about multiple line items for the same thing. “Your’e making us work too hard here, our warehouse people are getting frustrated when they have to go back to the bin they just visited to pickup something they could have grabbed the first time!”.

Naturally, our business folks wanted to please, so I got my marching orders to “Just consolidate the two line items into one and sum the quantity. It’ll be easy!”. Famous last words.

My first approach was too complicated: use a Linq Groupby clause to grab all the duplicates (along with quantities), then iterate all the line items and, for each item, if my list of duplicates contained the current Product ID, write the first line item with the grand total and suppress all subsequent items. Too complicated, I hope you’re still reading!

Here’s what I ended-up doing with ILookup:

int prodIndex = 0;

//Build the lookup
//Each entry will have the ProductId for the key
//The value is the list of all line items sharing that key!
ILookup<string, LineItem> distinctProductIds = ((IEnumerable)orderHeader.DetailList)
                                               .ToLookup(i => i.ProductID);
result = new ShippingProduct[distinctProductIds.Count()];
//Loop through the distinct ProductIDs:
foreach (var anOrderItem in distinctProductIds)
{
        //Grab the first product that might be duplicated
	LineItem firstInItemGoup = distinctProductIds[anOrderItem.Key].First();
	ShippingProduct curProd = new ShippingProduct();
	curProd.SKU = anOrderItem.Key;
	curProd.Name = firstInItemGoup.Description;
	//Here is where the order gets consolidated
	curProd.Quantity = distinctProductIds[anOrderItem.Key].Sum(i => i.Quantity).ToString();
	//... Copy all other remaining fields

	result[prodIndex++] = curProd;
}

Receiving Order Confirmations

OK, that wasn’t really tricky, but receiving the order confirmations from our eCommerce partner was. Fortunately, I used similar code for that, and that made life easier. One reason for the trickiness: our eCommerce partner doesn’t send the Product ID back to us, just the line item IDs. Also tricky because I had to credit the original order line items with the correct quantity.

ILookup<string, LineItem> ProductIDLookup = detailList.ToLookup(i => i.ProductID);

//Loop through each line item from partner and match, on LineItem ID (LineSeq == ItemNumber) to find a line item in Traverse that matches theirs
foreach (OrdersItem orderLineItem in egOrder.Items)
{
   decimal partnerQty = 0;
   decimal.TryParse(orderLineItem.Shipped, out partnerQty);
   if (partnerQty > 0)
   {
      LineItem curOrderDetail = detailList.FirstOrDefault(dtl => dtl.LineSeq.Value.ToString() == orderLineItem.ItemNumber);
      if (curOrderDetail != null)
      {
         string ProductID = curOrderDetail.ProductID;

         decimal lineItemTotQty = ProductIDLookup[ProductID].Sum(i => i.Quantity);
         if (lineItemTotQty !=partnerQty)
         {
            string msg = string.Format("Quantity shipped by does not match ");
            throw new ApplicationException(msg);
         }

         //There should only be one line item for this ProductID (product id) 
         //BUT, if there are duplicates, we will handle them all, so long as we do not exceed the quantity actually shipped by partner
         foreach (LineItem erpLineItem in ProductIDLookup[ProductID].OrderByDescending(l => l.UnitPrice))
         {
            decimal qtyToCredit = erpLineItem.Quantity;
            ReconcileOrder(erpLineItem, qtyToCredit, erpLineItem.TransId);
            partnerQty -= qtyToCredit;
            if (partnerQty <= 0)
               break;
         }
      }
   }
}

 Bottom Line

Any time you need to group members of your list on some field, whether it is Product ID, customer ID, zip code, or any other field, and then do something with each little group, the ILookup interface makes your life easier. For starts, building the list is painless. I’m surprised I never knew about it before!

NPR Puzzle Solved: June 29, 2014

Posted on Updated on

The Challenge:

This week’s challenge comes from Steve Baggish of Arlington, Mass., the father of the 11-year-old boy who created the challenge two weeks ago. Name a boy’s name and a girl’s name, each in four letters. The names start with the same letter of the alphabet. The boy’s name contains the letter R. Drop that R from the boy’s name and insert it into the girl’s name. Phonetically, the result will be a familiar two-word phrase for something no one wants to have. What is it?

Challenge URL

Link to the Sunday Puzzle

Overview

This was kind of interesting: even though my code won’t yield an exact solution, I was still able to produce a short list of potential solutions and then manually pick the best answer from that list. Main problem: the English language does not have simple pronunciation rules! I am using the Soundex algorithm to compare the pronunciation of phrases against words I construct (from boy/girl names), and the Soundex algorithm is only an approximation. Among its many drawbacks, Soundex doesn’t account for differences in vowel pronunciation. Creating your own replacement for Soundex is a lot of work, email me if you have a better way!

The soundex algorithm is a very useful algortithm; if you read this article, you will learn one good way to use it. You can download implementations of the algorithm from many places; I think I got my version from the Code Project. I have a link below to its description on Wikipedia.

You can also learn from my examples using Linq and .NET Dictionaries, both very useful tools to master!

Solution

Screen shot showing the solution
Candidate Solutions, Including the Winner, “Bad Breath”

Algorithms I Used

Soundex Algorithm for Mapping Pronunciation

The Soundex function takes a string for input and generates a number that corresponds to the pronunciation. Words that sound alike have similar soundex values. For example,

    • Soundex(“Robert”) == R163
    • Soundex(“Rupert”) == R163
    • Soundex(“Rubin”) == R150
  • Since Robert sounds a lot like Rubert, both names have the same soundex value.
  • As you can see, the soundex algorithm is just an approximate way to match similar words; according to soundex, “bated” sounds the same as as “bad” (because they both have the same soundex value of B300)

In my solution (explained below), I manipulate boy/girl names to construct candidate solutions. If my two candidate names have the same soundex values as a common phrase, then I know that boy and girl might be the answer I seek.

You could use the Soundex algorithm for a customer service app, when the customer talks with marbles in their mouth and your users need to quickly find possible spellings for what customer is telling them!

Levenshtein Distance for Measuring String Similarity

The Levenshtein Distance  indicates the similarity of two strings. The modified algorithm I used, computes a distance between 0 and 100; when the distance is 100, the two strings have nothing in common, when their distance is 0, they are identical. The lower the number, the more similar. For example,

  • LevenshteinDistance(“kitten”, “kitting”) == 28
  • LevenshteinDistance(“kitten”, “sitting”) == 42
  • ImprovedLevenshteinDistance(“kitten”, “asdf”) == 100

Thanks to Sten Hjelmqvist for his improved implementation of the Levenshtein altorithm, which you can download here: Code Project Article by Sten.

Why do I need the Levenshtein distance? Because the soundex algorithm is an imperfect mapping. For example, “Bated breath” and “bad breath” have the same soundex scores. But, by using Levenshtein, whenever to phrases have the same soundex score, I can pick the phrase which is closest to the original words. In this case, “BadBreth” is closer to “bad breath” than “bated breath”, because the the Levenshtein distance is smaller.

Sten uses Levenshtein distance to judge how much web pages have changed; apparently he crawls a lot of sites and needs to know who has been updated a lot.

Data

I got a list of boy and girl names from government web site a few years ago and now I can’t find the link. It might have been from the census bureau. If anyone wants a copy of my name files, leave a comment with your email and I can send them.

I got the list of phrases from various web sites and built my own list, using regular expressions and screen scraping techniques that I have explained in previous articles. Again, if anyone wants a copy of my list, leave a comment with your email.

My Approach

  1. Build a dictionary ‘phraseSoundexPairs’ containing:
    1. For dictionary value, use Phrases . Per puzzle instructions, only use 2-word phrases where each word has length == 4
    2. For the dictionary entry keys, use the Soundex values of the two words
      1. For example, the dictionary entry for “bad breath” will receive a key of “B300B630”, where B300 is the soundex of “bad” and B630 is the soundex of “breath”.
      2. Why did I construct my key this way? Because I can easily determine if any a candidate boy/girl matches (based on soundex) the pronunciation of any phrase in the dictionary. If the candidate soundex does not match any dictionary keys, then I know it is not a solution.
  2. Find all the boy names with length 4, make a list
  3. Find all the girl names with length 4, make a list
  4. Using Linq, join (match) the two lists on their first letters, rejecting any boy names that don’t contain the letter ‘r’
  5. Now, iterate the result of the join above
  6. For each match, remove the r from the boy and insert it into the girl’s name (there are 5 possible positions to insert it)
  7. For example, manipulate “brad” –> “bad”, “beth” –> “rbeth”, “breth”, “berth”, “betrh”, “bethr”
  8. Compute the soundex for the manipulated boy/girl name and check if it is in the dictionary. If so, we have a possible solution!
    • Soundex(“bad”) == B300
    • Soundex(“breth”) == B630
    • Candidate key –> “B300B630”
    • My dictionary has a matching key!
    • phraseSoundexPairs[“B300B630”] == “bad breath”, telling us we have the solution!
  9. Since my dictionary actually has several entries with similar soundex values, use the Levenshtein distance to find the best among those similar entries.
  10. For example, “bated breath” and “bad breath” both have the same soundex scores, but the Levenshtein distance is better for “bad breath”

The Top-Level Code

Take a look at the top-level code that solves the puzzle and writes the answer to a textbox named ‘tbAnswer’:

private void btnSolve_Click(object sender, RoutedEventArgs e) {
	tbAnswer.Clear();
	//Load the boy and girl names into the list, restricting them to length 4:
	List<string> boyList = LoadNameFile(
					DATA_FOLDER + BOY_FILE, NAME_LENGTH);
	List<string> girlList = LoadNameFile(
					DATA_FOLDER + GIRL_FILE, NAME_LENGTH);

	//Build a dictionary containing value= phrases, key=soundex scores
	Dictionary<string, string> phraseSoundexPairs 
				= BuildSoundex_PhraseDictionary();

	//Join the two lists on their first letters:
	var namePairs = from b in boyList join g in girlList
			on b.Substring(0, 1) equals g.Substring(0, 1)
			where b.Contains('r') &&
			      b.Length == 4 &&
			      g.Length == 4
			select new { Boy = b, Girl = g };

        //Iterate the joined result and construct candidate soundex values:
	foreach (var aPair in namePairs) {
		string boyName = aPair.Boy.Replace("r", "");
		string boySoundex = PuzzleHelper.Soundex(boyName);
		//There are 5 ways to construct each girl name, 
		//because there are 5 places to insert the 'r'
		for (int p = 0; p < 5; p++) {
			string girlName = aPair.Girl.Insert(p, "r");
			string girlSoundex = PuzzleHelper.Soundex(girlName);

			//Check if we have a dictionary entry composed
			//of both soundex values
			if (phraseSoundexPairs.ContainsKey(boySoundex + girlSoundex)) {
				//Use Levenstein distance to find the best match
				//if multiple are present:
				string closestPhrase = ClosestMatchFromCSVEntries(
						phraseSoundexPairs[boySoundex + girlSoundex], 
						boyName + girlName);
				tbAnswer.Text += 
                                 string.Format
				 ("Phrase: {0}/Boy:{1} Girl: {2} (Manipulated boy: {3}, Manipulated girl:{4})\n",
				 closestPhrase, aPair.Boy, aPair.Girl, boyName, girlName);
				break;
			}
		}
	}
}

Finding the Closest Match using Levenshtein Distance

This method takes an entry from the dictionary and splits it on commas. (Because when I have multiple phrases with the same soundex value, I append the new phrase using a comma to separate them.) Using Linq, I make a list of anonymous type containing the distance and the phrase, then choosing the first entry from that list, which has the lowest distance.

private string ClosestMatchFromCSVEntries(string csvPhrases, string combinedName) {
  var distList = from p in csvPhrases.Split(',')
                 select new { 
                       dist = PuzzleHelper.ImprovedLevenshteinDistance(combinedName, p), 
                       Phrase = p };

  return (from p in distList
          orderby p.dist
          select p
          ).First().Phrase;
}

Notes: Loading the Name Lists and Building the Dictionary

I don’t bother to explain function “LoadNameFile”; you should find it pretty easy to load the boy/girl names from file and construct a list; I only used simple C# IO methods. For examples, please refer to some of my previous puzzle solutions. Constructing the dictionary is a little bit tricker, only because of how I handle duplicate keys. It is easier just to display the code:

private static Dictionary<string, string> BuildSoundex_PhraseDictionary() {
	Dictionary<string, string> result = new Dictionary<string,string>();
	using (StreamReader sr = File.OpenText(DATA_FOLDER + PHRASE_FILE)) {
		while (sr.Peek() != -1) {
			string aLine = sr.ReadLine().ToLower().Trim();
			//Look for phrases containing exactly 1 space, 
			//ie. 2-word phrases
			if (aLine.Where(c => c == ' ').Count() == 1) {
				int p = aLine.IndexOf(' ');
				string word1 = aLine.Substring(0, p);
				string word2 = aLine.Substring(p + 1);
				if (PuzzleHelper.AllLettersAreAlpha(word1 + word2)) {
					string soundex = PuzzleHelper.Soundex(word1) 
						+ PuzzleHelper.Soundex(word2);
					//The only tricky part: if two phrases have the same
					//soundex, then append the second phrase after a comma:
					if (result.ContainsKey(soundex)) {
						result[soundex] += ", " + aLine;
					} else {
						result.Add(soundex, aLine);
					}
				}
			}
		}
	}
	return result;
}

Alternative Solution

I am not totally happy with this approach, due to difficulties with pronouncing English words, as discussed above. I considered using my list of common words mapped to their phonemes, which I downloaded from Carnegie Mellon University. (A phoneme is a way to pronounce a single syllable; the list of Phonemes for English consists of every possible sound we use in our common language.) That approach would have had the advantage of mapping words onto a value one-to-one. That would eliminate any duplicates from my list. Unfortunately, that list does not have any mappings for imaginary words like ‘breth’, so I can’t use it.

Summary

I hope you enjoyed this solution; Soundex has a number of uses, as does the Levenshtein distance. If you can use either of these in your code, leave a comment to that effect! I also hope you enjoyed my simple example of using Linq to join the list of boys and girls, I found that particular piece of code rather pleasing.

 

Solved the NPR Sunday Puzzle from June 1, 2014

Posted on Updated on

The Challenge

This challenge comes from listener Dan Pitt of Palo Alto, Calif. Take the name of a well-known American businessman — first and last names. Put the last name first. Insert an M between the two names. The result names a food item. What is it?

Challenge URL: http://www.npr.org/2014/06/01/317697851/getting-to-know-the-in-crowd

Facing the Challenge

Writing the code to solving this one was simple; the real challenge was getting the right data to solve! For some reason, businessmen don’t get much attention on Wikipedia or other sites. Wikipedia has lists of chefs, dentists, copyrighters, etc. but no list of businessmen! I was able to find a list of Entrepreneurs and CEOs, but those are not quite the same thing. Fortunately, the two lists were sufficient to solve the puzzle.

Likewise, finding the foods was not too easy, mainly because I found a huge list at USDA, but said list turned-out to be a list of processed foods that did not include the answer. Fortunately, I was able to solve using the next place I looked for foods, namely, a list of fruits from Wikipedia. I guess USDA doesn’t need to list fruits on their web site.

Nonetheless, it was quite satisfying to solve the puzzle!

Screen shot with solution
Screen shot showing the program in action and arriving at the solution, ‘Elon Musk’

Code to Solve the Puzzle, Given that the Data is Available

Let’s jump right into the guts of the issue, solving the puzzle. First, here is the code to test if a given name is a solution.

//This method will return true if fullName can be manipulated to match a key
//in the food dictionary, and will set the output variable 'answer'
public bool NameIsASolution(string fullName, 
                       Dictionary<string, string> foodDic, out string answer) {
    answer = string.Empty;
    //Only look at names that have at least one space, i.e. a first and last name
    if (fullName.Contains(' ')) {
        //Get the first and last name, using the 
	//positions of the first and last space in the name
        string firstName = fullName.Substring(0, fullName.IndexOf(' '));
        int p = fullName.LastIndexOf(' ');
        string lastName = fullName.Substring(p + 1, fullName.Length - p - 1);

        //Reverse the last and first names, while 
	//inserting the letter 'm'
        string candidate = lastName + 'm' + firstName;
        //The dictionary uses hashing technology to efficiently 
	//tell us if the candidate is a key in it:
        if (foodDic.ContainsKey(candidate)) {
            answer = foodDic[candidate];
        }
        return foodDic.ContainsKey(candidate);
    } else {
        return false;
    }
}

Note:

  • We identify the first name (if present) using IndexOf(‘ ‘)
  • In other words, find the first space in the name
  • We identify the last name using LastIndexof(‘ ‘)
  • Which won’t work if a name ends with “Jr.”, but we don’t care for this puzzle
  • The dictionary has already been constructed to strip-out spaces and punctionation in the key
  • But each value contains the original, unmanipulated fruit name

The code above gets called for every entry in two files:

  1. BUSINESSMAN_FILE
  2. CEO_FILE

Why two files? Because I elected to save the businessmen in a different format, including their country and area of specialization. So processing each line in the file requires slightly more work, to extract just the name from each line. In contrast, the food and CEO files list a simple name on each line.

Here is the code that fires when user clicks the button to solve the puzzle:

private void btnSolve_Click(object sender, RoutedEventArgs e) {
    //The original food name is the value, the key is the food in lower case with no spaces or punctuation
    Dictionary<string, string> foodDic = new Dictionary<string, string>();
    using (StreamReader sr = File.OpenText(FOOD_FILE)) {
        while (sr.Peek() != -1) {
            string aLine = sr.ReadLine();
            //Use Linq to get only letters (the Where-clause) and then reassemble
            //them from char to string (the Aggregate clause)
            string aKey = aLine
                                .ToLower()
                                .Where(c => c >= 'a' && c <= 'z')
                                .Aggregate("", (p, c) => p + c);
            if (!foodDic.ContainsKey(aKey)) {
                foodDic.Add(aKey, aLine);
            }
        }
    }

    //Process each line in the businessman file
    string answer = string.Empty;
    using (StreamReader sr = File.OpenText(BUSINESSMAN_FILE)) {
        while (sr.Peek() != -1) {
            string aLine = sr.ReadLine();
            //The full name is all the text preceding the first tab:
            string fullName = aLine.Substring(0, aLine.IndexOf('\t'));
            if (NameIsASolution(fullName.ToLower(), foodDic, out answer)) {
                txtAnswer.Text += answer + "<-->" + fullName + "\n";
                stbMessage.Text = "Solved!";
            }
        }
    }

    //Now process each line in the CEO file:
    using (StreamReader sr = File.OpenText(CEO_FILE)) {
        while (sr.Peek() != -1) {
            string aLine = sr.ReadLine();
            if (NameIsASolution(aLine.ToLower(), foodDic, out answer)) {
                txtAnswer.Text += answer + "<-->" + aLine + "\n";
                stbMessage.Text = "Solved!";
            }
        }
    }
}

As you can see, the code above

  • Opens the food file and builds a dictionary with the contents
  • Opens the businessmen file, finds the name on each line, and tests it to see if it is a solution
  • Does the same thing for the CEO file

Getting the Data

If you have read my blog before, you know that I generally adopt the same approach:

  1. Go to a URL with a .NET WebClient
  2. Download the html via the OpenRead method
  3. Identify the data using a regular expression
  4. Save the regular expression matches to a file

You can read one of my other blog entries to get the code for that technique; today I will just list the regular expression that I used for each URL.

URL Regex
http://en.wikipedia.org/ wiki/List_of_chief_executive_officers <li><a         #Literal
[^>]+         #Anything but >
>                #Literal
([^<]+)       #Capture group, anything but <
</a>           #Literal
(                 #Start capture group
\s*              #Whitespace
\(                #Escaped parenthesis
[^)]+           #Anything but )
\),               #Escaped parenthesis, comma
)?                #End capture group, optional
([^<]+)?     #Start capture group, anything but <, optional
</li>            #Literal
http://en.wikipedia.org/
wiki/
List_of_fruits
<span       #Literal
\s+           #Whitespace
id=”          #Literal
([^”]+)      #Capture group, anything but quote
”               #Literal
http://en.wikipedia.org/wiki/List_of_chief_executive_officers  <tr>         #Literal
\r?\n?        #Optional carriage return linefeed
<td><a      #Literal
[^>]+        #Anything but >
>               #Literal
.*?             #Anything, lazy match
</a></td> #Literal match
\r?\n?        #Optional carriage return linefeed
<td><a      #Literal
[^>]+         #Anything but >
>               #Literal
([^<]+)      #Capture Group, anything but <
</a></td> #Literal match
http://ndb.nal.usda.gov/ndb/foods  <a                             #Literal
\s+                             #Whitespace
href=”/ndb/foods/show #Literal
[^>]+                         #Anything but >
>                                #Literal
([^<]+)                       #Capture group, anything but <
</a>                           #Literal
\r?\n?                         #Optional carriage return linefeed
\s+                            #Whitespace
</td>                         #Literal

Summary

Use regular expressions to capture the data from web sites. Use string manipulation to reverse the businessman name and inject the letter ‘M’. Build a dictionary of foods using the food name as the key. For each manipulated businessman name, test if the dictionary contains that value as a key; if so, we have a winner!

 

Puzzle Victory – Word Synonym Pair/Remove Double-S

Posted on Updated on

The challenge:

What word, containing two consecutive S’s, becomes its own synonym if you drop those S’s?

Link to the challenge: http://www.npr.org/2014/01/26/266210037/take-synonyms-for-a-spin-or-pirouette

Screen Shot Showing Solution to Puzzle
Screen Shot Showing Solution to Puzzle

Fun With Linq!

Since I had a nice synonym list already, I was able to dive into this one and come up with an elegant solution using a couple lines of Linq (Language INtegrated Query). “Linq” is Microsoft’s built-in query language that allows you process all kinds of lists and a lot more.

Sorry, faithful readers, I don’t remember exactly where I got my synonym list, but you can get a similar list  here, even it is formatted differently than mine.

Algorithm

The following code snippet analyzes a single line from my file and puts the result into the variable ‘answer’ if a solution is found. It does almost all the work, yet takes only six lines. A detailed explanation follows, but if you examine it closely, you should be able to figure-out most of it:

var answer = from word1 in aLine.Split('|')
                let wp = new { Original = word1, WordNoSS = word1.Replace("ss", "") }
                join word2 in aLine.Split('|') on wp.WordNoSS equals word2
                where word1.Contains("ss") 
                    && !distinctAnswers.Contains(word1)
                select wp;

Detailed Explanation

  1. Open the synonym file fore reading, using a stream reader named ‘sr’
  2. Each line contains synonyms, separated by a pipe character, like this:
    • bloom|blossom|flower|flourish|coloration
  3. Within a  Linq query, split every line  from the file on the | character,
  4. Into an array,
  5. Name  each array entry ‘word1’
  6. Use the Linq ‘where’ operator to select only entries in the array containing ‘ss’
  7. Join the first Linq query against another query
  8. Which we build by again splitting the line, referring to its entries as ‘word2’
  9. If the join operation has any match whatsoever,
  10. Then we have a potential solution
  11. But reject any answer we have already discovered, by comparing current against previous entries from a list called ‘distinctAnswers’
  12. If our potential answer is not in the list, display the answer, and add it to the distinctAnswers list

Here’s the entire code listing

using System;
using System.Collections.Generic;   //For my list (of word pairs)
using System.Windows;
using System.IO;                    //To open synonyms file
using System.Windows.Input;         //To display hourglass while waiting
using System.Linq;

namespace Jan26_2014 {
    /// <summary>Code to solve the puzzleThe buttonEvent args();
    /// <summary>Code to solve the puzzle</summary>
    /// <remarks>
    /// Solution loads a list of synonyms from a file
    ///     Each line has several synonyms on it, separated by |
    /// Use split the line into an array of words and use linq on it.
    /// Linq provides an elegant solution that is also pretty efficient.
    /// </remarks>
    public partial class MainWindow : Window {

        private const string SYNONYM_FILE = 
                @"C:\Users\Randy\Documents\Puzzles\Data\Synonyms.txt";

        /// <summary>Default constructor</summary>
        public MainWindow() {
            InitializeComponent();
        }

        /// <summary>
        /// Fires when user clicks the Solve button
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        private void btnSolve_Click(object sender, RoutedEventArgs e) {
            Mouse.OverrideCursor = Cursors.Wait;
            txtAnswer.Clear();

            //Open the file for reading
            using (StreamReader sr = File.OpenText(SYNONYM_FILE)) {
                //Each line in the file contains words separted by |
                //There is an issue with the file (for our purposes)
                //For example, both these lines are in the file:
                //  - bloom|blossom|flower|flourish|coloration
                //  - blossom|bloom|flower|flourish

                //Use the list of distinct answers to deal with that issue and 
                //avoid reporting the same answer twice:
                List<string> distinctAnswers = new List<string>();
                //Keep reading while there is more to be read
                while (sr.Peek() != -1) {
                    string aLine = sr.ReadLine();

                    //Use Linq to join words lacking 'ss' against the same words in the the line 
                    var answer = from word1 in aLine.Split('|')
                           //Hold word1 in anonymous class/1)original value 2)word lacking 'ss'
                           let wp = new { Original = word1, WordNoSS = word1.Replace("ss", "") }
                           //Word2 values also come from the words in the line
                           join word2 in aLine.Split('|') on wp.WordNoSS equals word2
                           //Only use word1 if it has ss and we haven't found it already
                           where word1.Contains("ss") 
                                 && !distinctAnswers.Contains(word1)
                           select wp;

                    //'answer' is an IEnumerable (a kind of list) containing entries
                    //of an anonymous type; each entry has properties 'Original' and 'WordNoSS'
                    //If the list has any entry, each will be a valid answer pair
                    foreach (var wp in answer) {
                            txtAnswer.Text += wp.Original + " - " + wp.WordNoSS + "\n";
                            distinctAnswers.Add(wp.Original);
                    }
                }
            }
            MessageBox.Show("Done Solving", "Mission Accomplished",
                            MessageBoxButton.OK, MessageBoxImage.Information);
            Mouse.OverrideCursor = null;
        }
    }
}

Now, here’s the XAML, you should be able to run it except for the background, which you can either comment-out, or else download yourself from here.

A couple comments:

  • Since the solution was related to flowers, I decided to use a field of flowers for the background
  • And make the answer textbox partly transparent using an opacity setting of .5
  • The title bar uses a drop shadow effect for a little pizzaz
  • Like previous puzzle solutions, I elected to draw my own icon instead of linking to a file
<Window x:Class="Jan26_2014.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Synonym Pair with Double-S" Height="350" Width="710"
        WindowStartupLocation="CenterScreen" >

    <Grid>
        <!-- The Title with a drop shadow effect -->
        <TextBlock FontSize="36" Grid.ColumnSpan="3"
                Text="Two Synonyms Spelled Same Except for SS" 
                Foreground="DarkRed" HorizontalAlignment="Center">
            <TextBlock.Effect>
                <DropShadowEffect BlurRadius="5" Color="LightGray" 
                                    Opacity=".8" />
            </TextBlock.Effect>
        </TextBlock>

        <!-- The Challenge textblock -->
        <TextBlock Grid.Row="1" Grid.ColumnSpan="5" TextWrapping="Wrap" 
                   FontSize="16" Margin="3">
            <Bold>Challenge:</Bold> What word, containing two consecutive S's, 
            becomes its own synonym if you drop those S's?
        </TextBlock>

        <!-- The 3rd row has a label/textbox for the answer, plus the Solve button-->
        <Label Grid.Row="2" Content="Answer(s):" 
               Target="{Binding ElementName=txtAnswer}" 
               VerticalAlignment="Top" FontSize="16" FontWeight="Bold" />
        <TextBox Grid.Row="2" Grid.Column="1" Name="txtAnswer" 
                 TextWrapping="Wrap" AcceptsReturn="True" Opacity=".5" 
                 FontWeight="Black" FontSize="16"
                 />
        <Button Grid.Row="2" Grid.Column="2" Name="btnSolve" 
                Content="_Solve" Height="30" 
                FontSize="16" FontWeight="Black"
                VerticalAlignment="Top" Margin="3" Click="btnSolve_Click" />

        <!-- Comment-out the next 3 lines if you don't have a background file with that name -->
        <Grid.Background>
            <ImageBrush ImageSource="/Jan26_2014;component/Images/Flowers.jpg" Opacity=".6" />
        </Grid.Background>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition />
            <ColumnDefinition Width="auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
    </Grid>

    <!-- I elected to use a icon drawn in XAML, a flower was too hard -->
    <Window.Icon>
        <DrawingImage>
            <DrawingImage.Drawing>
                <GeometryDrawing Brush="Gold">
                    <GeometryDrawing.Pen>
                        <Pen Brush="Yellow" Thickness="1" />
                    </GeometryDrawing.Pen>
                    <GeometryDrawing.Geometry>
                        <EllipseGeometry RadiusX="25" RadiusY="25" />
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
            </DrawingImage.Drawing>
        </DrawingImage>
    </Window.Icon>
</Window>

That’s all, good luck!

Puzzle Solved! NPR Sunday Puzzle, 5-Letter Word + AY Anagrams to 7 Letter Related Word

Posted on Updated on

The Challenge:

Name something in five letters that’s generally pleasant, it’s a nice thing to have. Add the letters A and Y, and rearrange the result, keeping the A and Y together as a pair. You’ll get the seven-letter word that names an unpleasant version of the five-letter thing. What is it?

Link to the challenge.

Screen shot Showing the puzzle solution
Screen shot showing the solution to the puzzle

I had a bit of struggle meeting the requirement that the two words be related! Plus, the puzzle forced me to use a big dictionary. There are about 20-30 word pairs that you can re-arrange to anagram, but finding words that are related to each other required me to us an on-line dictionary.

Algorithm Part One: Finding Related Words

I used this dictionary web2.txt from puzzlers.org’s word lists page. It is pretty extensive and I needed it to get a rare word like ‘daymare’.

  1. Find all the 5-letter words
  2. Add ‘ay’ and sort the letters
  3. Put the result into a dictionary –> the key will be the sorted letters; the payload (value) will be the original word
  4. Now find all the 7-letter words which contain ‘ay’
  5. Sort their letters and check the dictionary to see if there is any matching key
  6. If so, we have a pair that might be related
Dictionary<string, string> anagramDic
			= new Dictionary<string, string>();
using (StreamReader sr = File.OpenText(WORD_FILE)) {
        //Keep reading to the end of the file
	while (sr.Peek() != -1) {
		string aWord = sr.ReadLine().ToLower();
		if (aWord.Length == 5) {
			//add 'ay' to the word and sort the letters:
			string key = ("ay" + aWord)
					 .Select(c => c)
					 .OrderBy(c => c)
					 .Aggregate("", (r, c) => r + c);

			//if it is already in the dictionary, append 
			//to the value with a comma
			if (anagramDic.ContainsKey(key)) {
				anagramDic[key] += "," + aWord;
			} else {
				anagramDic.Add(key, aWord);
			}
		}
	}
}
//Now get the 7-letter words and check whether their sorted letters are keys in the dictionary:
using (StreamReader sr = File.OpenText(WORD_FILE)) {
	while (sr.Peek() != -1) {
		string aWord = sr.ReadLine();
                //the word must be 7 letters long and contain ay
		if (aWord.Length == 7 && aWord.Contains("ay")) {

			string sorted = aWord
					.Select(c => c)
					.OrderBy(c => c)
					.Aggregate("", (r, c) => r + c);
                        //Two words are anagrams if they are the same after sorting
                        //Sort 'cat' --> 'act', sort 'tac' --> also 'act', therefore anagrams
                        //If the dictionary contains a key composed of the sorted letters, match!
			if (anagramDic.ContainsKey(sorted)) {
				//This method checks the web site to see if the 
				//words are related
				string synonym
					= UrbanDicSynonym(aWord, anagramDic[sorted]);
				if (!String.IsNullOrEmpty(synonym)) {
					answer += aWord + " - " + synonym + "\n";
				}
			}
		}
	}
}

Algorithm Part Two: Determining Whether Two Words are Rough Synonyms

I couldn’t find any decent lists of synonyms, and I only needed to look-up a few dozen word pairs, so I used an on-line dictionary: the Urban Dictionary. You can manipulate their query string to specify which word you want defined, and it will return your definition.

For example, if you paste “http://www.urbandictionary.com/define.php?term=daymare” into your browser address bar, it will build you a page with the definition of daymare. And you can substitute any word you like in place of ‘daymare’. That makes it very convenient to do look-ups in code!

With that in mind:

  1. Take your matching word pairs from part 1 of the algorithm, such as ‘daymare’ and ‘dream’
  2. Look-up the first of each pair on the Urban Dictionary, using a WebClient Object, which allows you to perform an OpenRead operation on the URL you build using the word candidate
  3. Parse the results of the results from  your OpenRead operation and extract the parts of the web page you need.
  4. For Urban Dictionary, the definitions are surrounded by special tags, like this:
    • <div class=definition:>A seriously bad dream, but experienced during daylight hours.</div>
  5. You can extract the text between those tags using a fairly simple regular expression.
  6. Take all the words you extract and see if any of them are your second word; if so, you probably have a winner!

Here’s the code that does that:

private string UrbanDicSynonym(string word1, string word2) {
	string urbanUrl = @"http://www.urbandictionary.com/define.php?term=";
	string pattern = @"
			<div                  #Literal match
			\s                    #Whitspace
			class=""definition""> #Literal match
			([^<]+?)              #Capture group/anything but <
			</div>                #Literal match"; 
	Regex reDef = new Regex(pattern, 
				RegexOptions.Multiline 
				| 
				RegexOptions.IgnorePatternWhitespace);
	string[] anagramList = word2.Split(',');
	using (WebClient wc = new WebClient()) {
		string url = urbanUrl + word1;
		using (StreamReader sr = new StreamReader(wc.OpenRead(url))) {
			string allText = sr.ReadToEnd();
			Match m = reDef.Match(allText);
			while (m.Success) {
				string payLoad = m.Groups[1].Value;
				foreach (string anAnagram in anagramList) {
					if (payLoad.IndexOf(anAnagram, 
						StringComparison.CurrentCultureIgnoreCase) > 0) {
							return anAnagram;
					}
				}
				m = m.NextMatch();
			}
		}
	}

	return "";
}

Some Nitty-Gritty

Since it is fairly slow to perform web look-ups using the techniques described above, I elected to display a progress bar and cancel button. That, in turn, made it almost necessary to use a Background worker process, which supports cancellation and also makes it easier to update the screen. Without using a separate thread, the progress bar won’t update until after your work is complete! Because the same thread that does the work is the thread that updates the screen.

Here’s the code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.IO;                            //To read/write to files
using System.Text.RegularExpressions;       //To match text patterns
using System.Net;                           //To fetch data from the web
using System.Windows.Input;                 //To manipulate the cursor
using System.ComponentModel;                //For Background Worker

namespace Jan_5_2014 {
    /// <summary>Logic to solve the puzzle</summary>
    public partial class MainWindow : Window {

        private const string WORD_FILE = @"C:\Users\Randy\Documents\Puzzles\Data\web2.txt";
        //You can get this file here: 
        //http://www.puzzlers.org/dokuwiki/doku.php?id=solving:wordlists:about:start

        BackgroundWorker _Wrker;

        /// <summary>Constructor</summary>
        public MainWindow() {
            InitializeComponent();
        }

        /// <summary>
        /// Responds when user clicks the Solve button
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        /// <remarks>
        /// First we find all the 5-letter words, append 'ay', 
        /// and build an anagram lookup dictionary with them, 
        /// using the sorted letters as the lookup key.
        /// 
        /// Then we find all the 7-letter words that contain 
        /// 'ay' and see if they have a match in the anagram dictionary.
        /// 
        /// If we find an anagram pair, look-up the 2nd word 
        /// in the urban dictionary and see if 1st word is contained
        /// any definition returned contains 
        /// 
        /// </remarks>
        private void btnSolve_Click(object sender, RoutedEventArgs e) {
            //Instantiate the background worker and wire-up its events
            _Wrker = new BackgroundWorker();
            _Wrker.WorkerReportsProgress = true;
            _Wrker.WorkerSupportsCancellation = true;
            _Wrker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Wrker_RunWorkerCompleted);
            _Wrker.ProgressChanged += new ProgressChangedEventHandler(Wrker_ProgressChanged);
            _Wrker.DoWork += new DoWorkEventHandler(Wrker_DoWork);

            txtAnswer.Clear();
            Mouse.OverrideCursor = Cursors.Wait;
            tbCurWord.Visibility = System.Windows.Visibility.Visible;
            prgBar.Visibility = System.Windows.Visibility.Visible;
            btnCancel.Visibility = System.Windows.Visibility.Visible;

            _Wrker.RunWorkerAsync();
        }

        void Wrker_DoWork(object sender, DoWorkEventArgs e) {
            string answer = "";
            //First build dictionary of 5-letter words using their 
            //sorted letters, plus "ay", as the key
            Dictionary<string, string> anagramDic
                        = new Dictionary<string, string>();
            //This loop will go really fast compared to the next loop
            int lineNum = 0;
            using (StreamReader sr = File.OpenText(WORD_FILE)) {
                while (sr.Peek() != -1) {
                    lineNum++;
                    string aWord = sr.ReadLine().ToLower();
                    if (aWord.Length == 5) {
                        //add 'ay' to the word and sort the letters:
                        string key = ("ay" + aWord)
                                     .Select(c => c)
                                     .OrderBy(c => c)
                                     .Aggregate("", (r, c) => r + c);

                        //if it is already in the dictionary, append 
                        //to the value with a comma
                        if (anagramDic.ContainsKey(key)) {
                            anagramDic[key] += "," + aWord;
                        } else {
                            anagramDic.Add(key, aWord);
                        }
                    }
                }
            }

            //Re-open the word list and get the 7-letter words
            int totalLines = lineNum;
            lineNum = 0;
            using (StreamReader sr = File.OpenText(WORD_FILE)) {
                while (sr.Peek() != -1) {
                    lineNum++;
                    string aWord = sr.ReadLine();
                    if (aWord.Length == 7 && aWord.Contains("ay")) {
                        int pctDone = (int)(100 * lineNum / totalLines);
                        //progress is current line # / total lines
                        //include current word
                        _Wrker.ReportProgress(pctDone, aWord);
                        string sorted = aWord
                                        .Select(c => c)
                                        .OrderBy(c => c)
                                        .Aggregate("", (r, c) => r + c);
                        if (anagramDic.ContainsKey(sorted)) {
                            string synonym
                                = UrbanDicSynonym(aWord, anagramDic[sorted]);
                            if (!String.IsNullOrEmpty(synonym)) {
                                answer += aWord + " - " + synonym + "\n";
                            }
                        }
                    }

                    //bail out if user cancelled:
                    if (_Wrker.CancellationPending) {
                        break;
                    }
                }
            }
            e.Result = answer;
        }

        /// <summary>
        /// Determines whether the UrbanDictionary lists word1 in
        /// the definitions returned by looking-up word 2
        /// </summary>
        /// <param name="word1">A word whose synomym might be in word 2</param>
        /// <param name="word2">A word (or CSV word list)
        /// that might be synonym of word 1</param>
        /// <returns>The word that is synonym of word 1</returns>
        /// <remarks>
        /// UrbanDictionary allows lookups passing the word you seek as part
        /// of the querystring. For example, you can look-up 'daymare' 
        /// with this querystring:
        ///     http://www.urbandictionary.com/define.php?term=daymare
        /// 
        /// Their site returns some html with the definitions embedded inside
        /// div tags that look like this:
        ///     <div class="definition">definition is here</div>
        /// 
        /// Note that word2 can be a single word or a CSV list of anagrams,
        /// such as "armed,derma,dream,ramed,"
        /// </remarks>
        private string UrbanDicSynonym(string word1, string word2) {
            string urbanUrl = @"http://www.urbandictionary.com/define.php?term=";
            string pattern = @"
                                <div                  #Literal match
                                \s                    #Whitspace
                                class=""definition""> #Literal match
                                ([^<]+?)              #Capture group/anything but <
                                </div>                #Literal match"; 
            Regex reDef = new Regex(pattern, 
                                    RegexOptions.Multiline 
                                    | 
                                    RegexOptions.IgnorePatternWhitespace);
            string[] anagramList = word2.Split(',');
            using (WebClient wc = new WebClient()) {
                string url = urbanUrl + word1;
                using (StreamReader sr = new StreamReader(wc.OpenRead(url))) {
                    string allText = sr.ReadToEnd();
                    Match m = reDef.Match(allText);
                    while (m.Success) {
                        string payLoad = m.Groups[1].Value;
                        foreach (string anAnagram in anagramList) {
                            if (payLoad.IndexOf(anAnagram, 
                                StringComparison.CurrentCultureIgnoreCase) > 0) {
                                    return anAnagram;
                            }
                        }
                        m = m.NextMatch();
                    }
                }
            }

            return "";
        }

        /// <summary>
        /// Fires when worker reports progress, updates progress bar 
        /// and current word
        /// </summary>
        /// <param name="sender">The background worker</param>
        /// <param name="e">Event args</param>
        void Wrker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
            prgBar.Value = e.ProgressPercentage;
            tbCurWord.Text = (string)e.UserState;
        }

        /// <summary>
        /// Fires when the background worker finishes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Wrker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            txtAnswer.Text = (string)e.Result;
            prgBar.Visibility = System.Windows.Visibility.Hidden;
            tbCurWord.Visibility = System.Windows.Visibility.Hidden;
            btnCancel.Visibility = System.Windows.Visibility.Hidden;
            Mouse.OverrideCursor = null;
            MessageBox.Show("Sovling complete!",
                            "Mission Accomplished",
                            MessageBoxButton.OK, MessageBoxImage.Exclamation);
        }

        /// <summary>
        /// Fires when user clicks the cancel button
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        private void btnCancel_Click(object sender, RoutedEventArgs e) {
            _Wrker.CancelAsync();
        }
    }
}

Now, here’s the XAML

<Window x:Class="Jan_5_2014.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        WindowStartupLocation="CenterScreen"
        Title="Anagram Synonym (with A + Y)" Height="300" Width="500">

    <DockPanel>        
        <!-- The status bar has a slider, progress bar and textblock -->
        <!-- We define it 1st so the next thing, the main content,
             will fill the remaining space -->
        <StatusBar DockPanel.Dock="Bottom">
            <Slider Name="sldZoom" Minimum=".5" Maximum="2.0" Width="100" 
                    ToolTip="Resize Form" Value="1" />
            <ProgressBar Name="prgBar" Minimum="1" Maximum="100" 
                         Visibility="Hidden" Background="DarkBlue" 
                         Height="20"
                         Foreground="Yellow" Width="200" />
            <TextBlock Name="tbCurWord" Visibility="Hidden" />
        </StatusBar>

        <!-- The Main grid, fills the DockPanel because it is added last-->
        <Grid>
            <!-- Page header -->
            <Border Grid.ColumnSpan="3">
                <Border.Background>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="LightYellow" Offset="0" />
                        <GradientStop Color="PaleGreen" Offset="1" />
                    </LinearGradientBrush>
                </Border.Background>
                <TextBlock FontSize="32" Text="5-Letter Anagram With A + Y" 
                           Foreground="Blue" HorizontalAlignment="Center">
                    <TextBlock.Effect>
                        <DropShadowEffect Opacity=".6" BlurRadius="5" 
                                          Color="LightBlue" />
                    </TextBlock.Effect>
                </TextBlock>
            </Border>

            <!-- Challenge -->
            <TextBlock Grid.Row="1" Grid.ColumnSpan="3" FontSize="14" 
                       TextWrapping="Wrap" Margin="3">
                <Bold>Challenge:</Bold> Name something in five letters 
                that's generally pleasant, it's 
                a nice thing to have. Add the letters A and Y, 
                and rearrange the result, keeping the A and Y 
                together as a pair. You'll get the seven-letter word 
                that names an unpleasant version of the five-letter 
                thing. What is it?
            </TextBlock>

            <!-- 3 Controls:  Solve button/label/answer textbox -->
            <Label Grid.Row="3" Content="A_nswer:" 
                   Target="{Binding ElementName=txtAnswer}" />
            <TextBox Grid.Row="3" Grid.Column="1" Name="txtAnswer" 
                     TextWrapping="Wrap" AcceptsReturn="True" 
                     VerticalScrollBarVisibility="Auto"
                     FontSize="14" />
            <StackPanel Orientation="Vertical" Grid.Row="3" 
                        Grid.Column="2" VerticalAlignment="Top">
                <Button Grid.Row="3" Grid.Column="2" Name="btnSolve" 
                    Height="30" VerticalAlignment="Top" 
                    Margin="3" Content="_Solve"
                    Click="btnSolve_Click" />
                <Button Name="btnCancel" Margin="3" Height="30"
                        Content="_Cancel"
                        Visibility="Hidden"
                        Click="btnCancel_Click" />
            </StackPanel>

            <!-- The Transform works with the slider to resize window -->
            <Grid.LayoutTransform>
                <ScaleTransform 
                    ScaleX="{Binding ElementName=sldZoom,Path=Value}"  
                    ScaleY="{Binding ElementName=sldZoom,Path=Value}" />
            </Grid.LayoutTransform>

            <!-- Define our rows and columns -->
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition Height="auto" />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto" />
                <ColumnDefinition />
                <ColumnDefinition Width="auto" />
            </Grid.ColumnDefinitions>

        </Grid>
    </DockPanel>

    <!-- In lieu of linking to a file, I elected to draw 
         the letters 'ABC' in Xaml -->
    <Window.Icon>
        <DrawingImage>
            <DrawingImage.Drawing>
                <GeometryDrawing>
                    <GeometryDrawing.Geometry>
                        <RectangleGeometry Rect="0,0,20,20" />
                    </GeometryDrawing.Geometry>
                    <GeometryDrawing.Brush>
                        <VisualBrush>
                            <VisualBrush.Visual>
                                <TextBlock Text="ABC" FontSize="8"
                                            Background="Yellow"
                                            Foreground="Blue" />
                            </VisualBrush.Visual>
                        </VisualBrush>
                    </GeometryDrawing.Brush>
                </GeometryDrawing>
            </DrawingImage.Drawing>
        </DrawingImage>
    </Window.Icon>
</Window>

that’s all

NPR Dancing Puzzle, December 1st, 2013

Posted on Updated on

Challenge: Name a dance. Change one of the letters to a U. The resulting letters can be rearranged to name an event at which this dance is done. What is it?

Although similar to the last several NPR puzzles, this one was a bit tough! Not for coding reasons, but because I couldn’t find a list of social events. I had to go to online thesauri repeatedly until I found the correct event name.

 

Hula (dance) --> Luau
Screen shot shows the solution to the puzzle.

The heart of the solution is to

  1. Build a dictionary of events, again using the sorted event name as the key and the original event name as the value
  2. Iterate the dance list
  3. For each dance name, form a candidate by
    1. Iterating the letters
    2. Replacing each letter with u
    3. Sorting the resulting string to form a candidate
    4. And checking whether that candidate is a key in our event name dictionary

If it seems like I am going a bit fast, please take a look a this previous post, where I used the same anagram dictionary technique to find other anagrams .

I created two extension methods, ‘Left‘ and ‘Right‘, to make my sample a bit easier to read:

  • MyString.Left(i) will return all the characters left of position i
  • MyString.Right(i) will return all the character to the right of position i

For example,

  • “hula”.Left(1) returns “h”.
  • “hula”.Right(1) returns “la”

I’ll show the (very simple) code for these two extension methods shortly, in the mean time, here’s the heart of the solution. This code snippet assumes

  1. That you have a file containing dance names named “DANCE_FILE”
  2. Which you have cleaned-up to exclude non-alpha characters like blanks and hyphens
  3. And that you have an event name dictionary “eventDic” similar to what I described above
//Open the dance file and process each dance name (one per line)
using (StreamReader sr = File.OpenText(DANCE_FILE)) {
    while (sr.Peek() != -1) {
        string aDance = sr.ReadLine().Trim().ToLower();

        //Iterate the dance name chracters, injecting u at every 
        //position, then sorting the result, to form a candidate:
        for (int i = 0; i < danceName.Length -1; i++) {
            //Combine the left side + u + right side and sort the result:
            string candidate = (danceName.Left(i) + "u" + danceName.Right(i))
                                .Select(c => c)
                                .OrderBy(c => c)
                                .Aggregate("", (t, c) => t + c);

            //If the candidate's sorted letters match the sorted letters of
            //an event name, that means they are anagrams!
            if (eventDic.ContainsKey(candidate)) {
                txtAnswer.Text += string.Format("Dance name: '{0}'; " 
                                    + "Event name: '{1}'\n", 
                                    aDance, eventDic[candidate]);
            }
        }
    }
}

If you’re gagging on the funky syntax (“.Select”, “.Aggregate”, etc.),  be advised that I skimmed over this because I explained it in a previous post and didn’t want to bore repeat visitors. These are Lambda Expressions, and I explain their usage in depth in this post, except that there I used Lambda Expressions instead of Linq syntax.

I promised to show my extension class, so here it is:

public static class StringExtensions {
    public static string Left(this string str, int rightPos) {
        if (rightPos <= 0)
            return "";
        else
            return str.Substring(0, rightPos);
    }

    public static string Right(this string str, int rightPos) {
        if (rightPos < str.Length - 1)
            return str.Substring(rightPos + 1);
        else
            return "";

    }
}

There is nothing in here too difficult; the point is that .NET allows you to write your own extension methods which behave as if they were part of the .NET string class. A simple class like this can help make your code more readable. Note the class is static, and also note the use of ‘this string str‘ as a parameter. That’s all it takes to make an extension method!

I used one more interesting bit of coding while getting a list of dance names. If you’ve read my previous puzzle articles, you know that I use regular expressions to get lists from web pages, frequently from Wikipedia. In this case, I got my list of dances from here. If you go to that link, you will see that the main list is preceded by a bunch of other links, which happen to match the same pattern as the dance names! In order to deal with this, I modified my regular expression to exclude any hits whose ‘payload’ starts with ‘List’. To do so, I used the Regex feature ‘Negative Lookahead‘, which was fairly new to me.

Here’s my documented regex to capture dance names from that page (negative lookahead highlighted):


<li><a                  #Literal match
\s+                     #Whitespace, one or more
href=\"/wiki/           #Literal match
[^>]+                   #Anything except >, one or more
>                       #Literal match
(                       #Start of our group
(?!List)                #Exclude matches starting with List
[^<]+                   #Anything except <
)                       #end of group
</a>                    #Literal match

In case you are totally baffled,

  • The regex will take raw HTML as input
  • That is, the HTML from Wikipedia
  • Which looks like this:
    <li><a href="/wiki/Hula" title="Hula">Hula</a></li>
  • This regular expression contains a capture group (starts on line 6)
  • Basically, it looks for
    • “<li><a” (line 1)
    • followed by space (line 2)
    • followed by href=\”/wiki/ (line 3)
    • followed by anything except a ‘>’  (this is a “negated character class“) (line 4)
    • Followed by > (line 5)
    • The parenthesis on line 6 starts a capture group
    • The negative lookahead (line 7) rejects any text starting with ‘article’
    • On line 8 we specify the text we will capture, namely: anything except <
    • Line 9 we close the capture group with a close-parenthesis
    • And specify that it must be followed by </a> (line 10)

I have listed the entire code below (138 lines, it took way longer for me to explain it here!)


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Text.RegularExpressions;
using System.Net;                           //For WebClient
using System.IO;

namespace Dec_1_2013 {
    /// <summary>
    /// Solves the puzzle by responding to each button click form the main page
    /// </summary>
    /// <remarks>
    /// Has code to fetch dance lists from two web pages and 
    /// then use those dance lists to /// solve the puzzle
    /// </remarks>
    public partial class MainWindow : Window {
        private const string DANCE_FILE = "DanceList.txt";
        public MainWindow() {
            InitializeComponent();
        }

        /// <summary>
        /// Fires when user clicks the button to get the dance list from the web
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        /// <remarks>
        /// Goes the the URL in the textbox, gets the page content, extracts matches 
        /// with the regex, saves to file
        /// </remarks>
        private void btnGetDances_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                            <li><a			    #Literal match
                            \s+				    #Whitespace, one or more
                            href=\""/wiki/	    #Literal match
                            [^>]+			    #Anything except >, one or more
                            >				    #Literal match
                            (				    #Start of our group
                            (?!List)		    #Exclude matches starting with List
                            [^<]+			    #Anything except <
                            )				    #end of group
                            </a>			    #Literal match
                            ";
            Regex reDance = new Regex(pattern
                    , RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);

            //Go to the web page, get the text, extract 
            //all the matches using the regex, save to file
            using (WebClient wc = new WebClient()) {
                using (StreamReader sr = new StreamReader(wc.OpenRead(txtDanceURL.Text))) {
                    string allText = sr.ReadToEnd();

                    //There exist a number of false matches after the last dance on 
                    //the page (marked by the text 'see also'), find the position now
                    int stopPos = allText.IndexOf("See also");
                    using (StreamWriter sw = File.CreateText(DANCE_FILE)) {
                        Match m = reDance.Match(allText);

                        //keep looping so long as we have more matches and have 
                        //not exceeded the last dance position
                        while (m.Success && m.Index < stopPos) {
                            sw.WriteLine(m.Groups[1].Value);
                            m = m.NextMatch();
                        }
                    }
                }                
            }
            MessageBox.Show("Done", "Dances Captured");
        }

        /// <summary>
        /// Fired when user clicks the button to solve the puzzle
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        /// <remarks>
        /// Load the dance list from file into a dictionary
        ///     - Each dictionary key is the sorted dance name
        ///     - Each dictionary value is the UNsorted dance name, the original value
        /// 
        /// Loop through every social event in the text box
        ///      - Try replacing each letter in the event name with 'u'
        ///      - Sort the result and check for a match in the dictionary
        ///      - If we have a hit, display in the textbox
        /// </remarks>
        private void btnSolve_Click(object sender, RoutedEventArgs e) {
            txtAnswer.Clear();

            //Snag the events from the textbox, put them into an array after splitting on comma char
            string[] events = txtEventNames.Text.ToLower().Split(',');
            Dictionary<string, string> eventDic = new Dictionary<string, string>();
            foreach (string evt in events) {
                //Remove non-alpha letters, sort, recombine into a new string
                string sortedEvent = evt.Where(ev => ev>= 'a' && ev <= 'z')
                                     .OrderBy(ev => ev)
                                     .Aggregate("", (t, c) => t + c);

                //store in the dictionary
                if (eventDic.ContainsKey(sortedEvent))
                    eventDic[sortedEvent] += ", " + evt;
                else
                    eventDic.Add(sortedEvent, evt.Trim());
            }

            //Now open the dance file and process each dance name (one per line)
            using (StreamReader sr = File.OpenText(DANCE_FILE)) {
                while (sr.Peek() != -1) {
                    string aDance = sr.ReadLine().Trim().ToLower();
                    //Remove non-alpha characters
                    string danceName = (aDance.Where(d => d >= 'a' && d <= 'z'))
                                                .Aggregate("", (t, c) => t + c);

                    //Iterate the dance name chracters, injecting u at every 
                    //position, to form a candidate
                    for (int i = 0; i < danceName.Length -1; i++) {
                        //Combine the left side + u + right side and sort the result:
                        string candidate = (danceName.Left(i) + "u" + danceName.Right(i))
                                            .Select(c => c)
                                            .OrderBy(c => c)
                                            .Aggregate("", (t, c) => t + c);

                        //If the candidate's sorted letters match the sorted letters of
                        //an event name, that means they are anagrams!
                        if (eventDic.ContainsKey(candidate)) {
                            txtAnswer.Text += string.Format("Dance name: '{0}'; "
                                                + "Event name: '{1}'\n", 
                                                aDance, eventDic[candidate]);
                        }
                    }
                }
            }
            MessageBox.Show("Done", "Matching Complete", MessageBoxButton.OK, 
                         MessageBoxImage.Exclamation);
        }

    }
}

And here is the XAML:


<Window x:Class="Dec_1_2013.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        WindowStartupLocation="CenterScreen"
        Title="Dec 1st, 2013" Height="460" Width="555">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition/>
            <ColumnDefinition  Width="auto" />
        </Grid.ColumnDefinitions>

        <Border Grid.ColumnSpan="3">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="DarkRed" Offset="0" />
                    <GradientStop Color="Crimson" Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <TextBlock HorizontalAlignment="Center" FontSize="36" 
                       Foreground="AntiqueWhite" 
                       Text="Dance Name to Event Name">
                <TextBlock.Effect>
                    <DropShadowEffect BlurRadius="4" Color="LightSalmon" 
                       Opacity="0.5" />
                </TextBlock.Effect>
            </TextBlock>
        </Border>

        <TextBlock Grid.Row="1" Grid.ColumnSpan="3" TextWrapping="Wrap" 
                   FontSize="14">
            <Bold>Next week's challenge: </Bold>
            Name a dance. Change one of the letters to a U. The 
            resulting letters can be rearranged to name an event at 
            which this dance is done. What is it?
        </TextBlock>

        <Label Grid.Row="2" Content="Dance _URL:" 
               Target="{Binding ElementName=txtDanceURL}" />
        <TextBox Grid.Row="2" Grid.Column="1" x:Name="txtDanceURL" 
                 Text="http://en.wikipedia.org/wiki/List_of_dances" />
        <Button Grid.Row="2" Grid.Column="2" x:Name="btnGetDances" 
                Content="Get _Dance List" Height="30"
                HorizontalAlignment="Right" Margin="3" Click="btnGetDances_Click" />

        <Label Grid.Row="3" Content="_Events:" 
               Target="{Binding ElementName=txtEventNames}" />
        <TextBox Grid.Row="3" Grid.Column="1" x:Name="txtEventNames" 
                 AcceptsReturn="True" TextWrapping="WrapWithOverflow"
                 VerticalAlignment="Stretch">
                social function, fundraiser, function, debutante ball, 
                photo opportunity, jubilation, funeral, groundbreaking, 
                graduation, sacramental union, communion, masquerade, ritual, 
                inauguration, presidential inauguration, papal inauguration,
                audience, changing of the guard, courtesy call, 
                investiture, yule, house party, circumstance,  nuptials, 
                induction, Maundy, bunfight, musical, burlesque, puppet play, 
                tournament, occurrence, masquerade ball, masque, masquerade party, 
                nauch, nautch, nautch dance, duet, pas de deux, pas de quatre, 
                ritual dance, ritual dancing, nude dancing, beaux arts ball,
                buffet, housewarming party, housewarming, costume party, 
                hurricane party, June Event, Open house, houseparty, 
                Symposium, Musical Occasion, Cultural festivals, Burning Man,
                Hula Festival, Saturnalia, Fat Tuesday, blowout, Ludi Saeculares,
                secular games, perambulation, church festival, luau, slumber party,
                reunion, banquet, musical soiree, soiree musicale
        </TextBox>
        <Button Grid.Row="4" Grid.Column="2" Content="_Solve" Height="30" 
                Margin="3" HorizontalAlignment="Right"
                VerticalAlignment="Top" Click="btnSolve_Click" x:Name="btnSolve"
                Width="{Binding ElementName=btnGetDances, Path=ActualWidth}" />

        <Label Grid.Row="4" Content="_Answer:" 
               Target="{Binding ElementName=txtAnswer}" />
        <TextBox Grid.Row="4" Grid.Column="1" x:Name="txtAnswer" />
    </Grid>
</Window>

NPR Puzzle Challenge – Solved in Code

Posted on Updated on

I enjoy listening to the NPR Sunday Puzzle (shouting-out the answers as I listen!), and it is even more fun to solve in code. Here’s my solution to the latest challenge, written in C# as a WPF application. You may enjoy it if you like code, want to learn new coding techniques, or just like puzzles

Laying-out the Puzzle Challenge

Next week’s challenge: This week’s challenge comes from listener Steve Baggish of Arlington, Mass. Think of a word meaning “quarrel” in which several of the letters appear more than once. Remove exactly two occurrences of every repeated letter, and the remaining letters can be rearranged to spell a new word meaning “quarrel.” What are the two words?

Link to the NPR challenge

General Approach

This puzzle is basically an anagram combined with string manipulation. The input is pretty easy, you can get synonyms for ‘quarrel’ from Google and a couple other places.

Checking for Anagrams in Code

There is an easy way to solve anagrams and a hard way. The hard way is probably the first way that comes to mind for most of us, namely

  1. Start with word 1, generate all possible combinations of the letters in the word
  2. Do the same thing for word 2
  3. Compare every combination from step 1 against step 2

Not only is that hard, but it is computationally expensive (generating word combinations is an O(n-squared) operation, not that we care for such a short-list).

The easy way is:

  1. Sort the letters in word 1
  2. Sort the letters in word 2
  3. Compare the result, if they are the same, word1 is an anagram of word2!

Solving Anagrams for Groups of Words

For this puzzle we need to determine if a any two of a number of words are anagrams. We can make it easy by modifying the approach above, instead of sorting each word just before comparing it to another word,

  1. Sort each word once
  2. Store the result in a dictionary
  3. Using the sorted letters as the entry’s key
  4. And the original word as the entry’s value 

Then, if I have an anagram candidate, I can look-it up in the dictionary, if the key is present, I know I have two matched-anagrams and can inspect the value to see what the other word is.

Sample Code Illustrates An Anagram Dictionary

//Declare a dictionary object that uses strings for keys and strings for values:
Dictionary<string, string> anagramDic = new Dictionary<string, string>();
 //add an entry into the dictionary using the sorted letters (act) as the key and the original word as the value
 anagramDic.Add("act", "cat");

 //we have another word that might be an anagram, 'tac'
 //we can check whether the word exists by sorting its letters to get 'act' and then checking whether it
 //already exists in the dictionary
 if (anagramDic.ContainsKey("act")) {
     //We can fetch the value using the key as an indexer
     string anagramPair = anagramDic["act"];
     Console.WriteLine(anagramPair + " is an anagram of tac");
 }

Side Note About Dictionaries

I love 🙂 dictionaries because they use hashing technology. That makes them ultra-efficient, and as you can see above, they are also quite easy to use.

One More Coding Technique

The code below uses a bit of Linq usage that is explained in the comments, but I will discuss it here for emphasis. The lines of code take a string variable named ‘aWord’ and sort the letters, storing the result into a new string called ‘sorted’:

string sorted = (from l in aWord
                 where l >= 'a' && l <= 'z'
                 orderby l
                 select l
                ).Aggregate("", (r, b) => r + b);
  • ‘aWord’ is a string such as ‘argument’, we select chars from it when we use the syntax “from l in aWord’
  • ‘l’ is an anonymous variable that has type char (.NET knows this so we do not need to explicitly declare it)
  • ‘orderby l’ has the effect of sorting the letters in aWord
  • Into an IEnumerable<char>, basically as list of chars
  • Since we don’t want a list, we use the ‘Aggregate‘ operator to concatenate the results into a string (because the + operator creates strings from chars)
    • The aggregate method used above takes two inputs:
      • the seed (in my casen an empty string “”)
      • And an anonymous function to perform the aggregation, namely “(r, b) => r + b)
        • The anonymous function takes two anonymous inputs, the current aggregated value (represented by r)
        • and the current value being included into the aggregate (represented by b)
        • The operation to be performed  is r + b, in other words, take the current aggregate value r and append the next value, b

Code to Solve the Puzzle

UI for my code to solve Will's puzzle
UI for my code to solve Will’s puzzle

I solved the puzzle in about 50 lines of code, then I added another 55 lines of documentation to it just to explain what is going on.

The full code is listed below, it uses a simple form with

  1. A TextBox ‘txtSynonyms’ containing synonyms to the word ‘quarrel’
  2. A button labeled ‘Solve’ whose click-event does all the work
  3. Another Textbox ‘txtAnswer’ to display the result it
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Nov17_2013 {
    /// <summary>Solves the puzzle using the synonyms in the textbox, 
	/// displaying the answer in the second textbox</summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        /// <summary>
        /// Fires when user clicks the button to solve the puzzle
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        /// <remarks>
        /// This solution relies on a simple technique to determine if two words are anagrams:
        ///     - Sort the letters of both words
        ///     - If their results are the same, they obviously are anagrams
        ///  
        /// Actually, I take it a bit further by sorting all the candidate words and storing them
        /// in a dictionary. The dictionary allows us to store a 'payload' (value) for each key
        /// we create. I use the sorted letters as the key and the original word as 
		/// the value, like this:
        /// 
        ///     Key                 Value
        ///     ---                 -----
        ///     aegmnrtu            argument
        ///     adeeegimnrst        disagreement
        ///     fghit               fight
        ///     achls               clash
        ///     defu                feud
        ///     addegiimnnnrsstu    misunderstanding
        /// 
        /// After constructing the dictionary I identify repeated letters in all the candidates,
        /// remove two of each, and check to see if the resulting word exists in the dictionary.
        /// If it does, the dictionary value is the anagram we seek!
        /// </remarks>
        private void btnSolve_Click(object sender, RoutedEventArgs e) {
            txtAnswer.Clear();
            Dictionary<string, string> sortedLettersDic = new Dictionary<string, string>();

            //The textbox contains words separated by commas, when I split them 
	    //I get an array with one entry per word
            string[] synonyms = txtSynonyms.Text.Split(',');

            //Process each synonym and put it into the dictionary.
            foreach (string aWord in synonyms) {
                //The "where-clause" cleans-up the candidate, removing blanks and hyphens
                //"orderby" sorts the letters
                //"Aggregate" concatenates the sorted chars into a string
                string sorted = (from l in aWord
                                 where l >= 'a' && l <= 'z'
                                 orderby l
                                 select l
                                ).Aggregate("", (r, b) => r + b);

                if (!sortedLettersDic.ContainsKey(sorted))
                    sortedLettersDic.Add(sorted, aWord);
            }

            //Now examine every dictionary entry, first building a list of repeated letters,
            //then using it to construct a potential anagram by removing two 
	    //of each repeated letter from the original
            foreach (KeyValuePair<string, string> kvp in sortedLettersDic) {
                List<char> repeatedLetters = new List<char>();
                for (int ndx = 1; ndx < kvp.Key.Length; ndx++) {
                    //examine the letter immediately prior to current letter
                    if (kvp.Key[ndx-1] == kvp.Key[ndx] 
                        && !repeatedLetters.Contains(kvp.Key[ndx])) {
                               repeatedLetters.Add(kvp.Key[ndx]);
                    }
                }

                if (repeatedLetters.Count > 0) {
                    //It is easier to remove letters from a List than a string, 
					//to put the letters into one:
                    //Also it is more efficient too, since strings are imutable in .NET
                    List<char> ltrs = new List<char>(kvp.Key);
                    foreach (char c in repeatedLetters) {
                        //remove exactly two of each repeated letter
                        ltrs.Remove(c);
                        ltrs.Remove(c);
                    }
                    //construct a new string by aggregating the list; use empth string for seed,
                    //and the inputs to the lambda expression are the running total (r) 
		    //and the current value (c):
                    string candidate = ltrs.Aggregate("", (r, c) => r + c);

                    //for example, if our candidate originally was 'misunderstanding', we now
                    //will have manipulated it as follows:
                    //step 1, sort:                     addegiimnnnrsstu
                    //step2, remove 2 repeated letters: aegmnrtu
                    //next step -                       is 'aegmnrtu' a key in the dictionary?

                    //Finally! If our candidate is in the dictionary, it means we have 
                    //constructed anagram of on of the other synonyms and solved the puzzle
                    if (sortedLettersDic.ContainsKey(candidate)) {
                        txtAnswer.Text += sortedLettersDic[candidate] + " - " + kvp.Value + " ";
                    }
                }
            }
        }
    }
}


In case you’re curious, I have also saved the XAML markup below so you can run the app. You don’t need any new references; I built it with VS 2010 but it should work in VS 2008, 2012 and 2013.

<Window x:Class="Nov17_2013.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        WindowStartupLocation="CenterScreen"
        FocusManager.FocusedElement="{Binding ElementName=btnSolve}"
        Title="Puzzle Nov 17, 2013" Height="430" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition />
            <ColumnDefinition Width="auto" />
        </Grid.ColumnDefinitions>
        <Border Grid.ColumnSpan="3" >
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="DarkRed" Offset="0" />
                    <GradientStop Color="Crimson" Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <TextBlock HorizontalAlignment="Center" FontSize="36" Text="Quarrel Manipulation Puzzle" Foreground="Ivory">
                <TextBlock.Effect>
                    <DropShadowEffect Color="MistyRose" BlurRadius="5" Opacity="0.35" />
                </TextBlock.Effect>
            </TextBlock>
        </Border>

        <!-- Row 1 -->
        <TextBlock Grid.Row="1" Grid.ColumnSpan="3" FontSize="14" TextWrapping="Wrap" Margin="3" >
             This week's challenge comes from listener Steve Baggish of Arlington, Mass. 
            Think of a word meaning "quarrel" in which several of the letters appear more than once. 
            Remove exactly two occurrences of every repeated letter, and the remaining letters can 
            be rearranged to spell a new word meaning "quarrel."
            <LineBreak />
            <LineBreak />
            What are the two words?
        </TextBlock>

        <!-- Row 2 -->
        <Label Grid.Row="2" Content="Puzzle _URL:" VerticalAlignment="Center" FontWeight="SemiBold" 
               Target="{Binding ElementName=tblPuzleUrl}" />
        <TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" x:Name="tblPuzleUrl" VerticalAlignment="Center" 
                   Text="http://www.npr.org/2013/11/17/245761408/more-fun-than-a-dead-rose" />

        <!-- Row 3 -->
        <Label Grid.Row="3" Content="Quarrel Synonyms:" FontWeight="SemiBold" Target="{Binding ElementName=txtSynonyms}" />
        <TextBox Grid.Row="3" Grid.Column="1" x:Name="txtSynonyms" TextWrapping="Wrap" >
                argument, disagreement, fight, clash, feud, contretemps, war of words, shouting match, informaltiff, 
            blowup, altercation, bickering, brawl, controversy, difference, difference of opinion, discord, dispute, 
            dissension, disturbance, shitstorm, falling-out, fracas, misunderstanding, row, ruckus, run-in, spat, 
            squabble, strife, struggle, tiff, tumult, vendetta, wrangle, affray, beef, breach, broil, combat, 
            commotion, complaint, contention, difficulty, disapproval, disputation, dissidence, dust, fisticuffs, 
            fray, fuss, hassle, objection, rhubarb, scrap, set-to, battle royal, brannigan
        </TextBox>
        <Button Grid.Row="3" Grid.Column="2" Content="_Solve" x:Name="btnSolve" VerticalAlignment="Top" 
                Height="30" Margin="3" Click="btnSolve_Click" />

        <!-- Row 4 -->
        <Label Grid.Row="4" Content="_Answer:" FontWeight="SemiBold" Target="{Binding ElementName=txtAnswer}" />
        <TextBox Grid.Row="4" Grid.Column="1" x:Name="txtAnswer" />
    </Grid>
</Window>