Puzzle Solving!

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 From November 24, 2013 – Solved

Posted on Updated on

I actually solved this one and didn’t post it because my answer looked wrong. I had never heard of the ‘Osage Orange’ tree; I thought it must be so exotic that Will wouldn’t have used it! That is why you see me going for more trees and more spices in the code.

Will’s Challenge

SpiceTree

The solution is very similar to my post from last week, again I use a dictionary for solving anagrams, utilizing the sorted word letters as the dictionary key and the unsorted word as the value. I also use regular expressions to extract tree lists and spice lists from the web. For a detailed explanation of these techniques, refer to my previous post.

The main algorithm is different only in that it combines two spices to check if they form an anagram of a tree. (Last week I didn’t need to combine anything before checking for anagrams). My algorithm is imperfect because it spits-out duplicate answers (I didn’t feel like expending the work to exclude dupes.)


    //Solve the problem by combining two of each spice (from my list 'spiceList'), 
    //sorting their letters, checking if the 
    //result is found in our tree dictionary 'treeDic'

    //Iterate our List<string> of string containing my spices
    foreach (string spice1 in spiceList) {
        foreach (string spice2 in spiceList) {
            if (spice1 != spice2) {
                //combine the two spices and sort their letters 
                //into our candidate:
                string candidate = (spice1 + spice2).ToLower()
                                    .Select(c => c)
                                    .OrderBy(c => c)
                                    .Aggregate("", (p, c) => p + c);

                //If the sorted pair matches a sorted tree name, they are anagrams
                if (treeDic.ContainsKey(candidate)) {
                    answer += string.Format("Tree: {0}/ Spice1: {1}/Spice 2: {2}\n",
                                    treeDic[candidate], spice1, spice2);
                }
            }
        }
    }

I have listed the code below in its entirety, in case you wish to replicate the results.


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

namespace Nov_24_2013 {
    /// <summary>
    /// Solves the puzzle of anagramming two spice names to a tree name
    /// </summary>
    public partial class MainWindow : Window {
        private const string TREE_FILE = "TreeList.txt";
        private const string SPICE_FILE = "SpiceList.txt";

        public MainWindow() {
            InitializeComponent();
        }

        /// <summary>
        /// Goes to wikipedia and gets a list of spices; saves to file
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Standard event args</param>
        private void btnGetSpices_Click(object sender, RoutedEventArgs e) {
            //This pattern matches the page, for the most part
            string pattern = @"
                                <(li)>	#Literal match on li, capturing the name
                                (?:		#Start of non-capturing group
                                <a.*?>	#Literal match on <a followed by anything up to >
                                (		#Start of group 2
                                [\w\s,]+	#word chars or whitespace
                                )</a>	#literal match
                                )?		#Close group 2, make it optional
                                (		#Start group 3
                                [\w\s]+		#word characters or whitespace or commas
                                )?		#close group 3, make it optional
                                .*?		#anything
                                </\1>	#closing tag containing backreference to group 1";
            using (WebClient wc = new WebClient()) {
                using (StreamReader sr = new StreamReader(wc.OpenRead(txtSpiceUrl.Text))) {
                    string allText = sr.ReadToEnd();
                    //the goodies start with 'Ajwain' and there are a number 
                    //of false matches prior
                    int p = allText.IndexOf("Ajwain");

                    Regex reSpice = new Regex(pattern, RegexOptions.Multiline 
                                        | RegexOptions.IgnorePatternWhitespace);
                    using (StreamWriter sw = File.CreateText(SPICE_FILE)) {
                        Match m = reSpice.Match(allText.Substring(p - 4));

                        while (m.Success) {
                            if (m.Groups[2].Length > 0)
                                sw.WriteLine(m.Groups[2].Value);
                            else
                                sw.WriteLine(m.Groups[3].Value);

                            m = m.NextMatch();
                        }
                    }
                }
            }
            MessageBox.Show("Done", "Spices Captured");
        }

        /// <summary>
        /// Goes to wikipedia and extracts the trees from the page, saving to file
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        private void btnGetTrees_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                                <i><a		#Literal match
                                .*?			#Anything, more than one, lazy
                                </a></i>	#Literal match
                                [^\w]+			#Any non-word characters
                                (			#Start of our group
                                [^<]+		#Anything except <, one or more
                                )			#End of our group
                                <			#Literal match
                                ";
            Regex reTree = new Regex(pattern, RegexOptions.IgnorePatternWhitespace 
                                | RegexOptions.Multiline);
            using (WebClient wc = new WebClient()) {
                using (StreamReader sr = new StreamReader(wc.OpenRead(txtTreeUrl.Text))) {
                    string allText = sr.ReadToEnd();
                    using (StreamWriter sw = File.CreateText(TREE_FILE)) {
                        Match m = reTree.Match(allText);
                        while (m.Success) {
                            sw.WriteLine(m.Groups[1].Value);

                            m  = m.NextMatch();
                        }
                    }
                }
            }
            MessageBox.Show("Trees Saved", "Mission Accomplished");
        }

        //Go to another web site and get more tree names, insert into our file
        private void btnGetMoreTrees_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                            <TD><font		#Literal match
                            [^>]+			#Anything except >
                            ><a			#Literal match
                            [^<]+			#Anything except <
                            >			#Literal match
                            [\s\n]*			#Optional whitespace and newline
                            ([\w\s,\-]+)	        #Capture group - whitespace,words,commas,hyphen
                            ,</a>			#Literal match
                            ";
            Regex reTrees = new Regex(pattern, RegexOptions.IgnorePatternWhitespace 
                                        | RegexOptions.Multiline);
            //We will use this to replace some bad characters in the matches identified above
            Regex reBad = new Regex(@",\s\r\n\s+");

            using (StreamWriter sw = File.AppendText(TREE_FILE)) {
                using (WebClient wc = new WebClient()) {
                    using (StreamReader sr = new StreamReader(wc.OpenRead(txtMoreTreesUrl.Text))) {
                        string allText = sr.ReadToEnd();
                        Match m = reTrees.Match(allText);
                        while (m.Success) {
                            string aTree = m.Groups[1].Value;
                            sw.WriteLine(reBad.Replace(aTree, " "));
                            m = m.NextMatch();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Goes to a different web site and gets more spice names
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnGetMoreSpices_Click(object sender, RoutedEventArgs e) {
            string fName = SPICE_FILE;
            string pattern = @"
                            <tr>			#Literal match
                            [\s\n]*			#Whitespace or newline
                            <td			#Literal match
                            [^>]*			#Not >
                            ><font		#Literal match
                            [^>]+			#Not >
                            >			#Literal match
                            (?:<span>)?	        #Non-capturing group, optional, literal match on <span>
                            <a			#Literal match
                            [^>]+			#Not >
                            >			#Literal match
                            ([^<]+)			#our group, anything except <
                            </a			#Literal match
                                ";
            List<string> spiceList = new List<string>();
            using (StreamReader sr = File.OpenText(fName)) {
                while (sr.Peek() != -1) {
                    string aLine = sr.ReadLine();
                    spiceList.Add(aLine);
                }
            }

            Regex reSpice = new Regex(pattern, RegexOptions.Multiline 
                | RegexOptions.IgnorePatternWhitespace);
            using (WebClient wc = new WebClient()) {
                StreamReader sr = new StreamReader(wc.OpenRead(txtMoreSpices.Text));
                string allText = sr.ReadToEnd();

                Match m = reSpice.Match(allText);
                while (m.Success) {
                    int p = spiceList.BinarySearch(m.Groups[1].Value);
                    if (p < 0)
                        spiceList.Insert(~p, m.Groups[1].Value);

                    m = m.NextMatch();
                }
            }

            File.Delete(fName);
            using (StreamWriter sw = File.CreateText(fName)) {
                foreach (string aSpice in spiceList) {
                    sw.WriteLine(aSpice);
                }
            }
        }

        /// <summary>
        /// Solves the puzzle
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSolve_Click(object sender, RoutedEventArgs e) {
            txtResult.Text = "";
            string answer = "";
            string twoWordPattern = @"
                                    ^\w+	#Beginning of line, word characters
                                    (?:\s+|-)	#Non-capturing grp, whitespace or hyphen
                                    \w+$	#Letter characters, end of line
                                    ";
            Regex reHas2Words = new Regex(twoWordPattern, RegexOptions.Multiline 
                                            | RegexOptions.IgnorePatternWhitespace);

            //Build a dictionary of trees, using the sorted tree name as the key
            //and the original tree name as the value
            Dictionary<string, string> treeDic = new Dictionary<string, string>();
            using (StreamReader sr = File.OpenText(TREE_FILE)) {
                while (sr.Peek() != -1) {
                    string aLine = sr.ReadLine();
                    string[] tokens = aLine.Split(';');
                    foreach(string aTree in tokens) {
                        if (reHas2Words.IsMatch(aTree)) {
                            string sortedLtrs = (from c in aTree.TrimEnd().ToLower()
                                                 orderby c
                                                 where c >= 'a' && c <= 'z'
                                                 select c
                                                ).Aggregate("", (p, c) => p + c);
                            if (!treeDic.ContainsKey(sortedLtrs)) {
                                treeDic.Add(sortedLtrs, aTree);
                            }
                        }
                    }
                }
            }

            //Build a list of spices
            List<string> spiceList = new List<string>();
            using (StreamReader sr = File.OpenText(SPICE_FILE)) {
                while (sr.Peek() != -1) {
                    string aSpice = sr.ReadLine();
                    spiceList.Add(aSpice);
                }
            }

            //Now solve the problem by combining two of each spice and checking
            //if the result is found in our tree dictionary
            foreach (string spice1 in spiceList) {
                foreach (string spice2 in spiceList) {
                    if (spice1 != spice2) {
                        string candidate = (spice1 + spice2).ToLower()
                                                .Select(c => c)
                                                .OrderBy(c => c)
                                                .Aggregate("", (p, c) => p + c);
                        if (treeDic.ContainsKey(candidate)) {
                            answer += string.Format("Tree: {0}/ Spice1: {1}/Spice 2: {2}\n",
                                            treeDic[candidate], spice1, spice2);
                        }
                    }
                }
            }

            txtResult.Text = answer;
        }

        /// <summary>
        /// The original list sometimes had multiple trees per line, separted
        /// by semicolon; fix it here to create one tree per line.
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Standard event args</param>
        private void btnFixTrees_Click(object sender, RoutedEventArgs e) {
            List<string> allTrees = new List<string>();
            using (StreamReader sr = File.OpenText(TREE_FILE)) {
                while (sr.Peek() != -1) {
                    string[] tokens = sr.ReadLine().Split(';');
                    foreach (string aTree in tokens) {
                        allTrees.Add(aTree);
                    }
                }
            }
            var fixedList = (from t in allTrees
                             orderby t
                             select t
                            ).Distinct();

            File.Delete(TREE_FILE);
            using (StreamWriter sw = File.CreateText(TREE_FILE)) {
                foreach (string aTree in fixedList) {
                    sw.WriteLine(aTree);
                }
            }
        }

    }
}    
    

The XAML for my form follows


<Window x:Class="Nov_24_2013.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        WindowStartupLocation="CenterScreen"
        Title="Tree Anagrams to Two Spices" Height="550" Width="725">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition />
            <ColumnDefinition Width="auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Border Grid.ColumnSpan="3" >
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="Blue" Offset="0" />
                    <GradientStop Color="CornflowerBlue" Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <TextBlock HorizontalAlignment="Center" FontSize="32" 
                       Text="Tree Name Anagrams to Two Spices" 
                       Foreground="Chartreuse">
                <TextBlock.Effect>
                    <DropShadowEffect BlurRadius="3" Color="AliceBlue" 
                                      Opacity="0.4" ShadowDepth="3" />
                </TextBlock.Effect>
            </TextBlock>
        </Border>

        <TextBlock Grid.Row="1" Grid.ColumnSpan="3" TextWrapping="Wrap" 
                   FontFamily="Times New Roman" FontSize="18">
            <Bold>Challenge:</Bold> Name a tree whose letters can be rearranged 
            to spell two herbs or spices. What are they? Hint: The tree has a two-word name
        </TextBlock>

        <Label Grid.Row="2" Content="_Challenge URL:" Target="{Binding ElementName=txtChallengeUrl}" />
        <TextBlock Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" 
            x:Name="txtChallengeUrl" 
            Text="http://www.npr.org/2013/11/24/246933624/we-plant-the-seed-you-pick-the-tree" />

        <Label Grid.Row="3" Content="_Spice URL:" Target="{Binding ElementName=txtSpiceUrl}" />
        <TextBox Grid.Row="3" Grid.Column="1" x:Name="txtSpiceUrl"
                 Text="http://en.wikipedia.org/wiki/List_of_spices" />
        <Button Grid.Row="3" Grid.Column="2" Content="_Get Spices" 
                x:Name="btnGetSpices" Click="btnGetSpices_Click" 
                Height="30" Margin="3" />

        <Label Grid.Row="4" Content="Mo_re Spices:" 
               Target="{Binding ElementName=txtMoreSpices}" />
        <TextBox Grid.Row="4" Grid.Column="1" x:Name="txtMoreSpices" 
                 Text="http://spicesinc.com/t-list-of-spices.aspx" />
        <Button Grid.Row="4" Grid.Column="2" x:Name="btnGetMoreSpices" 
                Content="_More Spices" Height="30" Margin="3"
                Click="btnGetMoreSpices_Click" />

        <Label Grid.Row="5" Content="_Tree URL:" Target="{Binding txtTreeUrl}" />
        <TextBox Grid.Row="5" Grid.Column="1" x:Name="txtTreeUrl" 
                Text="http://en.wikipedia.org/wiki/List_of_trees_and_shrubs_by_taxonomic_family" />
        <Button Grid.Row="5" Grid.Column="2" x:Name="btnGetTrees" 
                Height="30" Margin="3" Content="Get Trees" Click="btnGetTrees_Click" />

        <Label Grid.Row="6" Content="_More Trees:" Target="{Binding ElementName=txtMoreTreesUrl}" />
        <TextBox Grid.Row="6" Grid.Column="1" x:Name="txtMoreTreesUrl" 
                Text="http://www.ces.ncsu.edu/depts/hort/consumer/factsheets/trees-new/common_namesa_e.html"/>
        <Button Grid.Row="6" Grid.Column="2" Height="30" Margin="3" 
                Content="M_ore Trees" x:Name="btnGetMoreTrees" 
                Click="btnGetMoreTrees_Click" />

        <Label Grid.Row="7" Content="_Result:" Target="{Binding ElementName=txtResult}" />
        <TextBlock Grid.Row="7" Grid.Column="1" x:Name="txtResult" TextWrapping="Wrap" />
        <Button Grid.Row="7" Grid.Column="2" Content="_Solve" x:Name="btnSolve" 
                Height="30" Margin="3" Click="btnSolve_Click" />

        <Button Grid.Row="8" Grid.Column="2" Content="_Fix Trees" 
                x:Name="btnFixTrees" Height="30" Margin="3" Click="btnFixTrees_Click" />
    </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>