Visual Studio

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!

NPR Puzzle: Famous Person with 4 Double-Letters in Name

Posted on

The Challenge:

Name a famous person whose first and last names together contain four doubled letters — all four of these being different letters of the alphabet. Who is it? For example, Buddy Holly’s name has two doubled letters, D and L.

Link to challenge

Screen shot showing solution
Screen shot showing some of the over 40 matching names

Well, that was kind of fun, and I enjoyed the results. For starts, I found over 40 answers. For seconds, I love some of the names that qualify, like ‘Bobby “Boogaloo” Watts‘ (2 b’s, 2 o’s, 2 o’s again, 2 t’s), ‘Dimebag Darrell Abbott‘, ‘Boccaccio Boccaccino‘, ‘Blind Mississippi Morris’ or ‘William M. “Boss” Tweed‘ (qualifies because the trailing ‘m’ in William pairs with his middle initial. While they all qualify, I’m sure the person Will was thinking of was Tennessee Williams.

Algorithm

As you might guess, solving the puzzle is easy if you have the right list. But getting that list was quite a challenge! First, let’s discuss processing the list(s) of names to solve.

As you can see from my screen shot, I elected to make a number of name lists, motivated by the mechanics of capturing them from Wikipedia. In terms of solving the puzzle, that just means we need a loop to process each file. We read each file one name at a time. When we read a name, we use another loop to process the letters in that name. By ‘processing’, I mean that we examine the letter and compare it to the previous letter, if they match, we increase our counter. After processing the line, if our counter equals 4, we have a winner!

List answerList = new List();
string folder = Path
                .GetDirectoryName(Assembly
                .GetExecutingAssembly()
                .Location);

//Get all the text files in the exe folder, hold result in an array:
string[] fileList = Directory.GetFiles(folder, "*.txt");
//process every file we found
foreach(string aFile in fileList) {
    using (StreamReader sr = File.OpenText(aFile)) {
        while (sr.Peek() != -1) {
            string aPerson = sr.ReadLine();
            //Remove blanks convert to lower case
            string letters = aPerson.ToLower().Replace(" ", "");
            //reject some bad names that contain bad letters like ( or ,
            if (!letters.Contains("(") && !letters.Contains(",")) {
                char lastLtr = letters[0];
                int hitCount = 0;
                //loop through the letters
                for (int i = 1; i < letters.Length; i++) {
                     if (letters[i] == lastLtr) {
                         hitCount++;
                         //avoid triple letters by resetting previous
                         lastLtr = '';
                     } else {
                         lastLtr = letters[i];
                     }
                 }
                 if (hitCount >= 4) {
                    //look for previous entries:
                    NameFilePair prev = answerList.FirstOrDefault(n => n.Person == aPerson);
                    if (prev == null) {
                        NameFilePair nfp = new NameFilePair {
                            FileName = Path.GetFileName(aFile),
                            Person = aPerson
                        };
                        answerList.Add(nfp);
                    }
                }
            }
        }
    }
}

Downloading the Name Lists

As I mentioned above, the hardest part was getting the lists of names. As usual, Wikipedia had the most complete list, but scattered across many pages. I was able to start at this page http://en.wikipedia.org/wiki/Lists_of_people_by_nationality and programmatically follow the links on every page to other pages. Basically, my code loads the main page, identifies two kinds of links on the page:

  • Links to other pages of name lists
  • Links to actual articles about people

On most pages, the same pattern is used for each kind of link, though I had to do a bit of manual cleaning. As usual, I utilized regular expressions to match the text.

Here’s the complete code listing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.IO;                        //To open/close files
using System.Net;                       //For WebClient
using System.Text.RegularExpressions;   //To match text patterns
using System.Windows.Input;             //For Mouse override cusor
using System.ComponentModel;            //For Background worker
using System.Reflection;                //To get exe path

namespace Jan19_2014 {
    /// <summary>Downloads names and solves the puzzle</summary>
    public partial class MainWindow : Window {

        //Allows us to display a cancel button and update the status bar on different thread
        BackgroundWorker _WikipediaFetcher;

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

        /// <summary>
        /// Fires when user clicks the button 'Solve'
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        /// <remarks>
        /// Iterates all the files in the project folder having 'txt' extension,
        /// approximately 705 total.
        /// 
        /// Examines every name in each file by
        ///     - Concatenating the letters
        ///     - Looping through the result
        ///     - Comparing the last letter to the current
        ///     - Counting as a hit when the prev letter matches current
        ///     - Adding to the answer list when the hit count equals 4
        /// </remarks>
        private void btnSolve_Click(object sender, RoutedEventArgs e) {
            List<NameFilePair> answerList = new List<NameFilePair>();
            string folder = Path
                            .GetDirectoryName(Assembly
                                            .GetExecutingAssembly()
                                            .Location);
            
            //Get all the text files in the exe folder:
            string[] fileList = Directory.GetFiles(folder, "*.txt");
            //process every file we found
            foreach(string aFile in fileList) {
                using (StreamReader sr = File.OpenText(aFile)) {
                    while (sr.Peek() != -1) {
                        string aPerson = sr.ReadLine();
                        //Remove blanks convert to lower case
                        string letters = aPerson.ToLower().Replace(" ", "");
                        //reject some bad names that contain bad letters
                        if (!letters.Contains("(") && !letters.Contains(",")) {
                            char lastLtr = letters[0];
                            int hitCount = 0;
                            //loop through the letters
                            for (int i = 1; i < letters.Length; i++) {
                                if (letters[i] == lastLtr) {
                                    hitCount++;
                                    //avoid triple letters by resetting previous
                                    lastLtr = '';
                                } else {
                                    lastLtr = letters[i];
                                }
                            }

                            if (hitCount >= 4) {
                                //look for previous entries:
                                NameFilePair prev = answerList.
                                            FirstOrDefault(n => n.Person == aPerson);
                                if (prev == null) {
                                    NameFilePair nfp = new NameFilePair {
                                        FileName = Path.GetFileName(aFile),
                                        Person = aPerson
                                    };
                                    answerList.Add(nfp);
                                }
                            }
                        }
                    }
                }

            }
            //bind the results to the grid
            grdAnswer.ItemsSource = answerList;

            MessageBox.Show("Solving Done", "Mission Accomplished", 
                MessageBoxButton.OK, MessageBoxImage.Information);
        }

        /// <summary>
        /// Fired when user clicks the button to get Wikipedia people
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        /// <remarks>
        /// Sets-up the background worker and fires it off.
        /// </remarks>
        private void btnGetWikipediaRecursive_Click(object sender, RoutedEventArgs e) {
            if ((string)btnGetWikipediaRecursive.Content == "_Go") {
                _WikipediaFetcher = new BackgroundWorker();
                _WikipediaFetcher.WorkerSupportsCancellation = true;
                _WikipediaFetcher.WorkerReportsProgress = true;
                _WikipediaFetcher.RunWorkerCompleted += 
                    new RunWorkerCompletedEventHandler(WikipediaFetcher_RunWorkerCompleted);
                _WikipediaFetcher.ProgressChanged += 
                    new ProgressChangedEventHandler(WikipediaFetcher_ProgressChanged);
                _WikipediaFetcher.DoWork += new DoWorkEventHandler(WikipediaFetcher_DoWork);

                tbProgress.Visibility = System.Windows.Visibility.Visible;
                btnGetWikipediaRecursive.Content = "_Cancel";

                _WikipediaFetcher.RunWorkerAsync(txtWikipediaPeopleUrl.Text);
            } else {
                btnGetWikipediaRecursive.Content = "_Go";
                _WikipediaFetcher.CancelAsync();
            }
        }

        /// <summary>
        /// Fires when the background worker is kicked-off
        /// </summary>
        /// <param name="sender">The worker</param>
        /// <param name="e">Event args containing the URL as the e.Argument</param>
        /// <remarks>
        /// This method will start visiting a Wikipedia page that has links
        /// to other pages which may, in turn, contain other pages.
        /// 
        /// In general, each page has two types of payload we care about:
        ///     - the link to another page
        ///     - a person's name
        /// We identify each with a regular expression, but in this method,m
        /// we initialize the to regular expressiosn and merely pass them
        /// to a subroutine 'CapturePeople'.
        /// 
        /// This method iterates a number of links on the main page (which
        /// is rather irregular, so we set-up boundaries within which we search)
        /// and calls CapturePeople on each. CapturePeople is recursive,
        /// so it will handle any additional links therein.
        /// </remarks>
        void WikipediaFetcher_DoWork(object sender, DoWorkEventArgs e) {
            string peoplePattern = @"
                                    <li><a			#Literal match
                                    [^>]+?			#Anything except >
                                    >				#Literal match
                                    ([^<]+?)		#Capture group, anything except <
                                    <				#Literal match
                                    ";
            Regex rePeople = new Regex(peoplePattern, 
                RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);

            List<string> visitedUrlList = new List<string>();
            using (WebClient wc = new WebClient()) {
                string wikiUrl = e.Argument as string;
                using (StreamReader sr = new StreamReader(wc.OpenRead(wikiUrl))) {
                    string allText = sr.ReadToEnd();
                    int p0 = allText.IndexOf("class=\"div-col columns column-width");
                    int p1 = allText.IndexOf("id=\"See_also");
                    string payLoad = allText.Substring(p0, p1 - p0);


                    string linkPattern = @"
                            <li><a		#Literal match
                            \s			#Whitespace
                            href=""		#Literal match
                            (/wiki/List_of_[^""]+?)	#Capture group, anything but quote
                            ""			#Literal match
                            ";
                    Regex reLink = new Regex(linkPattern, 
                        RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
                    Match m = reLink.Match(payLoad);
                    while (m.Success) {
                        _WikipediaFetcher.ReportProgress(0, m.Groups[1].Value);
                        if (_WikipediaFetcher.CancellationPending) {
                            break;
                        }
                        CapturePeople(m.Groups[1].Value, visitedUrlList, 
                                        wc, reLink, rePeople);

                        m = m.NextMatch();
                    }
                }
            }
        }

        /// <summary>
        /// Captures the people listed on the URL in question, writing to a file based on
        /// the URL, and follows any links on the page to additional lists.
        /// </summary>
        /// <param name="listUrl">The Url to load and capture people form</param>
        /// <param name="visitedUrlList">List of links we have already visited</param>
        /// <param name="wc">Web client to fetch web pages</param>
        /// <param name="reLink">Regex that identifies a typical Wikipedia list link</param>
        /// <param name="rePeople">Regex that identifies a typical Wikipedia person name</param>
        private void CapturePeople(string listUrl, List<string> visitedUrlList, 
                                WebClient wc, Regex reLink, Regex rePeople) {
            string url = "http://www.Wikipedia.org" + listUrl;
            int ndx = visitedUrlList.BinarySearch(url);
            if (ndx < 0)
                visitedUrlList.Insert(~ndx, url);
            else
                return;

            try {
                using (StreamReader sr = new StreamReader(wc.OpenRead(url))) {
                    string allText = sr.ReadToEnd();

                    //Limit matching to prior to these two IDs; generally
                    //any false matches that follow are for things like referecnes
                    int p = allText.IndexOf("id=\"See_also");
                    int p2 = allText.IndexOf("id=\"References");
                    Match linkMatch = reLink.Match(allText);
                    //iterate the matches
                    while (linkMatch.Success && linkMatch.Index < p) {
                        if (_WikipediaFetcher.CancellationPending)
                            return;

                        _WikipediaFetcher.ReportProgress(0, listUrl);
                        if (!url.EndsWith(linkMatch.Groups[1].Value)) {
                            CapturePeople(linkMatch.Groups[1].Value, 
                                    visitedUrlList, wc, reLink, rePeople);
                        }
                        linkMatch = linkMatch.NextMatch();
                    }

                    _WikipediaFetcher.ReportProgress(0, url);
                    Match m = rePeople.Match(allText);
                    int p3 = allText.IndexOf("class=\"mw-headline");
                    if (p3 >= 0)
                        allText = allText.Substring(p3);
                    string fName = listUrl.Substring(listUrl.LastIndexOf("/") + 1) + ".txt";
                    using (StreamWriter sw = File.CreateText(fName)) {
                        while (m.Success && (p == -1 || m.Index < p) &&
                                            (p2 == -1 || m.Index < p2)) {
                            string person = m.Groups[1].Value;
                            sw.WriteLine(person);

                            m = m.NextMatch();
                        }
                    }
                }
            } catch (Exception ex) {
                Console.WriteLine(ex.Message);
            }

        }

        /// <summary>
        /// Fired when the background worker has something to report,
        /// namely, the URL it is processing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void WikipediaFetcher_ProgressChanged(object sender, ProgressChangedEventArgs e) {
            tbProgress.Text = (string)e.UserState;
        }

        /// <summary>
        /// Fired when the background worker is done
        /// </summary>
        /// <param name="sender">The background worker</param>
        /// <param name="e">Event args</param>
        void WikipediaFetcher_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            tbProgress.Visibility = System.Windows.Visibility.Hidden;
            MessageBox.Show("Done Capturing People", "Mission Accomplished", 
                             MessageBoxButton.OK, MessageBoxImage.Information);
        }
    }
}

Here’s my little class, used to hold a person and the file they came from:

    public class NameFilePair {
        public string Person { get; set; }
        public string  FileName { get; set; }
    }

Finally, here’s the XAML

<Window x:Class="Jan19_2014.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Famous Person With 4 Double Letters"
        WindowStartupLocation="CenterScreen"
        Height="380" Width="925">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        
        <!-- Left side/vertical letters -->
        <Border Background="Brown">
            <TextBlock FontSize="48" FontFamily="Impact" 
                       Foreground="LightGreen" VerticalAlignment="Top" >
                <ItemsControl ItemsSource="PUZZLE" />
            </TextBlock>
        </Border>
        
        <DockPanel Grid.Column="2">
            <!-- Status bar holds slider and progress display -->
            <StatusBar DockPanel.Dock="Bottom">
                <Label Content="Zoom:" Target="{Binding ElementName=sldZoom}" />
                <Slider Name="sldZoom" Width="50" Minimum=".5" Maximum="3" Value="1" />
                <TextBlock Name="tbProgress" Visibility="Hidden" />
            </StatusBar>
            
            <!-- Nested grid hods everything except the status bar and vertical PUZZLE -->
            <Grid >
                <Grid.LayoutTransform>
                    <!-- Works with the slider to enlarge/shrink the page -->
                    <ScaleTransform   ScaleX="{Binding ElementName=sldZoom,Path=Value}" 
                                      ScaleY="{Binding ElementName=sldZoom,Path=Value}" />
                </Grid.LayoutTransform>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto" />
                    <ColumnDefinition />
                    <ColumnDefinition Width="auto" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto" />
                    <RowDefinition Height="auto" />
                    <RowDefinition Height="auto" />
                    <RowDefinition />
                </Grid.RowDefinitions>
                
                <Border Grid.ColumnSpan="3">
                    <Border.Background>
                        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                            <GradientStop Color="BurlyWood" Offset="1" />
                            <GradientStop Color="Brown" Offset="0" />
                        </LinearGradientBrush>
                    </Border.Background>
                    <TextBlock Text="Famous Person's Name with 4 Double Letters" 
                               FontFamily="Arial" FontStyle="Italic" 
                               FontSize="40" TextWrapping="Wrap" 
                               Foreground="LightGreen" VerticalAlignment="Center" >
                    </TextBlock>
                </Border>

                <TextBlock Grid.Row="1" Grid.ColumnSpan="3" TextWrapping="Wrap" 
                           FontSize="14" Margin="5 0">
                    <Bold>Next week's challenge from Ed Pegg Jr. of MathPuzzle.com:</Bold>
                    Name a famous person whose first and last names together 
                    contain four doubled letters — all four 
                    of these being different letters of the alphabet. Who 
                    is it? For example, Buddy Holly's name has two doubled 
                    letters, D and L.
                </TextBlock>
                
                
                <Label Grid.Row="2" Content="_Wikipedia Start URL:" 
                       Target="{Binding ElementName=txtWikipediaPeopleUrl}" 
                       VerticalAlignment="Center" />
                <TextBox Grid.Row="2" Grid.Column="1" Name="txtWikipediaPeopleUrl" 
                         Text="http://en.wikipedia.org/wiki/Lists_of_people_by_nationality"
                         VerticalAlignment="Center" />
                <Button Grid.Row="2" Grid.Column="2" Content="_Go" 
                        Name="btnGetWikipediaRecursive"
                        Height="30" Margin="3" Click="btnGetWikipediaRecursive_Click" />


                <Label Grid.Row="3" Content="_Answers:" 
                       Target="{Binding ElementName=txtAnswer}" />
                <DataGrid Grid.Row="3" Grid.Column="1" Name="grdAnswer" 
                          AutoGenerateColumns="True" />
                <Button Grid.Row="3" Grid.Column="2" Name="btnSolve" 
                        Content="_Solve" Height="30" Margin="5" 
                        Click="btnSolve_Click" Grid.ColumnSpan="2"
                        
                        VerticalAlignment="Top" />
            </Grid>
        </DockPanel>            
    </Grid>

    <!-- Build our own Icon using letters '4-2', meaing 4 doubles -->
    <Window.Icon>
        <DrawingImage>
            <DrawingImage.Drawing>
                <GeometryDrawing>
                    <GeometryDrawing.Geometry>
                        <RectangleGeometry Rect="0,0,20,20" />
                    </GeometryDrawing.Geometry>
                    <GeometryDrawing.Brush>
                        <VisualBrush>
                            <VisualBrush.Visual>
                                <TextBlock Text="4-2" FontSize="8"
                                                Background="Brown"
                                                Foreground="LightGreen" />
                            </VisualBrush.Visual>
                        </VisualBrush>
                    </GeometryDrawing.Brush>
                </GeometryDrawing>
            </DrawingImage.Drawing>
        </DrawingImage>
    </Window.Icon>
</Window>

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

Solved! NPR Sunday Puzzle for December 22, 2013

Posted on Updated on

I had to struggle a bit on this one, mainly because every time I searched for ‘filmmaker’ on the web, I got a list of feature-film producers and directors. After I wised-up and downloaded a list of  documentary producers, solving was a piece of cake!

 

Solution - Sun +Filmmaker
Screen shot shows the answer to the puzzle (plus a bunch of buttons to get filmmakers from ever more locations!)

The Challenge

Think of a well-known filmmaker, first and last names. Add “S-U-N” before this person’s first name and last name. In each case, you’ll form a common English word. Who is the filmmaker?

Link to the challenge

Algorithm

The algorithm is straightforward; assume you have:

  1. A file containing filmmaker names, one per line
  2. A List<string> of words starting with ‘sun’, sorted
  3. A Textbox ‘txtAnswer’ to display the answer

Simply loop through the list of names, grabbing the first name and last name with the ‘Split’ function, which will give you an array containing each name part as a separate entry in the array. Concatenate ‘sun’ with the last name and check if the resulting candidate exists in the list – an efficient technique is to use BinarySearch, which returns the index where the candidate is found (but returns negative number if not found). If the last name exists in the array, try the same on the first name. If they both exist in the list, we have a winner!

Code That Solves the Puzzle with a File of Filmmakers ‘fName’ and a List of Words ‘sunList’

private void CheckFileAgainstSunWords(string fName, List sunList) {
    //Open the file of filmmakers:
    using (StreamReader sr = File.OpenText(fName)) {
        ///Loop while there is data to read:
        while (sr.Peek() != -1) {
            //Get a filmmaker name, converting to lower case and removing leading/trailing blanks:
            string aLine = sr.ReadLine().Trim().ToLower();
            //Split the name by the space characters, put each word into an array slot:
            string[] nameParts = aLine.Split(' ');
            //the last name is the last entry in the arrary; prepend 'sun to form a candidate
            string candidate2 = "sun" + nameParts[nameParts.Length - 1];
            //The list 'sunList' is sorted, a binary search will return a non-negative position of
            //the candidate is in the list
            if (sunList.BinarySearch(candidate2) >= 0) {
                //We got a hit on the filmmaker last name, so try the first 
                //name (zeroth entry in the array)
                string candidate1 = "sun" + nameParts[0];
                //As above, if the list contains the candidate, binary search returns a non-negative index
                if (sunList.BinarySearch(candidate1) > 0) {
                    //Append the filmmaker name to the text box
                    txtAnswer.Text += aLine;
                }
            }
        }
    }
}

Complete Code List

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

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

        /// <summary>Where we save the film maker names</summary>
        private const string PRODUCER_FILE_NAME = "Producers.txt";

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

        /// <summary>
        /// Fires when user clicks the button
        /// </summary>
        /// <param name="sender">The Button</param>
        /// <param name="e">Event args</param>
        private void btnGetProducers_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                                ^<b><a		#Literal text
                                \s+?		#Whitespace, one or more
                                [^>]+?		#Anything except >
                                >		#Literal text
                                ([^<]+?)	#Capture group - anything except <
                                </a></b>	#Literal match
                                ";
            Mouse.OverrideCursor = Cursors.Wait;
            CaptureTextFromUrl(txtProducerUrl.Text, pattern, PRODUCER_FILE_NAME);

            Mouse.OverrideCursor = null;
            MessageBox.Show("Producers Downloaded", "Mission Complete", 
                             MessageBoxButton.OK, MessageBoxImage.Information);
        }

        /// <summary>
        /// Gets list of film makers from a different url
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        private void btnWikipediaProducers_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                                <li><a		#Literal match
                                [^>]+?		#Anything exept >, one or more
                                >		#Literal match
                                ([^<]+?)	#Capture group, anything but <
                                </a>		#Literal match
                                ";
            CaptureTextFromUrl(txtWikipediaUrl.Text, pattern, PRODUCER_FILE_NAME);
        }

        /// <summary>
        /// Gets more film makers from different URL
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        private void btnGetMoreProducers_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                            \d{4}\s		#4digits and a space
                            /\s+?		#/ and space
                            ([^)]+?)	        #Capture group - anything but )
                            \)			#Escaped close-parenthesis
                            ";
            CaptureTextFromUrl(txtMoreProducers.Text, pattern, PRODUCER_FILE_NAME);
        }

        /// <summary>
        /// Gets more directors from different URL
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        private void btnMoreDirectors_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                            csv_column_4"">	#Literal text
                            ([^<]+?)		#Capture group, anything but <
                            <			#Literal match
                            ";
            List<string> previousNameList = new List<string>();
            using (StreamReader sr = File.OpenText(PRODUCER_FILE_NAME)) {
                while (sr.Peek() != -1) {
                    previousNameList.Add(sr.ReadLine().ToLower());
                }
            }

            Regex re = new Regex(pattern, 
                       RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
            using (WebClient wc = new WebClient()) {
                using (StreamReader sr = new StreamReader(wc.OpenRead(txtMoreDirectors.Text))) {
                    string allText = sr.ReadToEnd();
                    Match m = re.Match(allText);
                    while (m.Success) {
                        int p = m.Groups[1].Value.IndexOf(',');
                        if (p > 0) {
                            string director = m.Groups[1].Value.Substring(p + 2).ToLower() 
                                              + " " 
                                              + m.Groups[1].Value.Substring(0, p).ToLower();
                            p = previousNameList.BinarySearch(director);
                            if (p < 0) {
                                previousNameList.Insert(~p, director);
                            }
                        }

                        m = m.NextMatch();
                    }
                }
            }

            using (StreamWriter sw = File.CreateText(PRODUCER_FILE_NAME)) {
                foreach (string prodName in previousNameList) {
                    sw.WriteLine(prodName);
                }
            }
        }

        /// <summary>
        /// Standardized way to capture text from specified url using the regex
        /// and saving to the specified file
        /// </summary>
        /// <param name="url">Web location where data is retrieved from</param>
        /// <param name="rePattern">Regex customized for the page in question</param>
        /// <param name="fName">File to add the data to</param>
        private void CaptureTextFromUrl(string url, string rePattern, string fName) {
            List<string> previousNameList = new List<string>();
            using (StreamReader sr = File.OpenText(fName)) {
                while (sr.Peek() != -1) {
                    previousNameList.Add(sr.ReadLine().ToLower());
                }
            }

            Regex re = new Regex(rePattern, 
                     RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
            using (WebClient wc = new WebClient()) {
                using (StreamReader sr = new StreamReader(wc.OpenRead(url))) {
                    string allText = sr.ReadToEnd();
                    Match m = re.Match(allText);
                    while (m.Success) {
                        string aName = m.Groups[1].Value.ToLower();
                        int p = previousNameList.BinarySearch(aName);
                        if (p < 0) {
                            previousNameList.Insert(~p, aName);
                        }

                        m = m.NextMatch();
                    }
                }
            }

            using (StreamWriter sw = File.CreateText(PRODUCER_FILE_NAME)) {
                foreach (string prodName in previousNameList) {
                    sw.WriteLine(prodName);
                }
            }
        }

        /// <summary>
        /// Solves the puzzle using a list of English words
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSolve_Click(object sender, RoutedEventArgs e) {

            //Load the  word list and record 'Sun' words in a list
            string fName = @"C:\Users\Randy\Documents\Puzzles\Data\2of12inf.txt";
            List<string> sunList = new List<string>();
            using (StreamReader sr = File.OpenText(fName)) {
                while (sr.Peek() != -1) {
                    string aLine = sr.ReadLine().ToLower();
                    if (aLine.StartsWith("sun")) {
                        sunList.Add(aLine);
                    } else if (string.Compare(aLine, "sun") > 0) {
                        break;
                    }
                }
            }

            txtAnswer.Clear();
            //Bounce the film maker file against the director list
            CheckFileAgainstSunWords(PRODUCER_FILE_NAME, sunList);
            string directorFile = @"C:\Users\Randy\Documents\Puzzles\Data\DirectorList.txt";

            //Try again with a different file
            CheckFileAgainstSunWords(directorFile, sunList);
            MessageBox.Show("Solving Complete", "Mission Accomplished", 
                            MessageBoxButton.OK, MessageBoxImage.Exclamation);
        }

        /// <summary>
        /// Unified way to check a film maker name against the word list
        /// </summary>
        /// <param name="fName">File containing film maker names</param>
        /// <param name="sunList">List of words starting with 'sun'</param>
        private void CheckFileAgainstSunWords(string fName, List<string> sunList) {
            using (StreamReader sr = File.OpenText(fName)) {
                while (sr.Peek() != -1) {
                    string aLine = sr.ReadLine().Trim().ToLower();
                    string[] nameParts = aLine.Split(' ');
                    string candidate2 = "sun" + nameParts[nameParts.Length - 1];
                    if (sunList.BinarySearch(candidate2) >= 0) {
                        string candidate1 = "sun" + nameParts[0];
                        if (sunList.BinarySearch(candidate1) > 0) {
                            txtAnswer.Text += aLine;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Removes dupes and makes the names lower case
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnFixNameLIst_Click(object sender, RoutedEventArgs e) {
            List<string> all = new List<string>();
            using (StreamReader sr = File.OpenText(PRODUCER_FILE_NAME)) {
                while (sr.Peek() != -1) {
                    string aLine = sr.ReadLine().ToLower();
                    int p = all.BinarySearch(aLine);
                    if (p < 0) {
                        all.Insert(~p, aLine);
                    }
                }
            }

            using (StreamWriter sw = File.CreateText(PRODUCER_FILE_NAME)) {
                foreach (string aName in all) {
                    sw.WriteLine(aName);
                }
            }
        }

        /// <summary>
        /// Fires when user clicks the button to get more filme makers
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        private void btnEvenMore_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                            ^\s+		#Beginning of line, whitespace
                            ([\w\s]+?)		#Capture group, letters and spaces
                            (?:\s-)?		#optional space hyphen
                            \s			#Whitespace
                            <br>		#Literal match
                            ";
            CaptureTextFromUrl(txtEvenMore.Text, pattern, PRODUCER_FILE_NAME);
        }

        /// <summary>
        /// Fires when user clicks the button, gets a list of film makers
        /// </summary>
        /// <param name="sender">The button</param>
        /// <param name="e">Event args</param>
        private void btnGetDocumentaryMakers_Click(object sender, RoutedEventArgs e) {
            string pattern = @"
                            <li><a		#Literal match
                            [^>]+?		#Anything but >, 1 or more
                            >			#Literal match
                            ([^<]+?)	        #Capture group, anything but <
                            </a>		#Literal match
                            ";
            CaptureTextFromUrl(txtDocumentaryMakers.Text, pattern, PRODUCER_FILE_NAME);
        }

    }
}

Here’s the Xaml!

<Window x:Class="Dec22_2013.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        WindowStartupLocation="CenterScreen"    
        Title="Puzzle Dec. 22, 2013" Height="450" Width="655">

    <Grid>
        <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>
        <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="DarkGoldenrod" Offset="0" />
                    <GradientStop Color="Gold"  Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <TextBlock FontSize="40" Text="Sun + Movie Maker Puzzle" 
                       HorizontalAlignment="Center" Foreground="GreenYellow">
                <TextBlock.Effect >
                    <DropShadowEffect />
                </TextBlock.Effect>
            </TextBlock>
        </Border>

        <TextBlock Grid.Row="1"  Grid.ColumnSpan="3" FontSize="13" 
                   TextWrapping="Wrap" Margin="3">
            <Bold>Next week's challenge from listener Andrew Chaikin of San Francisco: </Bold>
            Think of a well-known filmmaker, first and last names. Add "S-U-N" before 
            this person's first name and last name. In each case, you'll form a common 
            English word. Who is the filmmaker?
        </TextBlock>

        <Label Grid.Row="2" Content="IMDB Producer _Url:" 
                 Target="{Binding ElementName=txtProducerUrl}" />
        <TextBox Grid.Row="2" Grid.Column="1" x:Name="txtProducerUrl" 
                 Text="http://www.imdb.com/list/ZFLM-BUjNlA/" />
        <Button Grid.Row="2" Grid.Column="2" Height="30" Margin="3" x:Name="btnGetProducers" 
                Content="Get _Producers" Click="btnGetProducers_Click" />

        <Label Grid.Row="3" Content="Wikipedia Producer URL:" 
                Target="{Binding ElementName=txtWikipediaUrl}" />
        <TextBox Grid.Row="3" Grid.Column="1" x:Name="txtWikipediaUrl"
                 Text="http://en.wikipedia.org/wiki/List_of_film_producers" />
        <Button Grid.Row="3" Grid.Column="2" x:Name="btnWikipediaProducers" 
                Height="30" Margin="3"
                Content="Wikipedia Producers"
                Click="btnWikipediaProducers_Click" />

        <Label Grid.Row="4" Content="_More Producers" 
               Target="{Binding ElementName=txtMoreProducers}" />
        <TextBox Grid.Row="4" Grid.Column="1" x:Name="txtMoreProducers"
                 Text="http://www.theyshootpictures.com/gf1000_all1000films_ex1000.htm" />
        <Button Grid.Row="4" Grid.Column="2" x:Name="btnGetMoreProducers" 
                Height="30" Margin="3" Content="M_ore Producers" 
                Click="btnGetMoreProducers_Click" />

        <Label Grid.Row="5" Content="More _Directors:" 
               Target="{Binding ElementName=txtMoreDirectors}" />
        <TextBox Grid.Row="5" Grid.Column="1" x:Name="txtMoreDirectors"
                 Text="http://www.theyshootpictures.com/gf1000_all1000films_table.php" />
        <Button Grid.Row="5" Grid.Column="2" x:Name="btnMoreDirectors" Height="30" 
                Margin="3" Content="More _Directors" Click="btnMoreDirectors_Click" />

        <Label Grid.Row="6" Content="Even More:" />
        <TextBox Grid.Row="6" Grid.Column="1" x:Name="txtEvenMore" 
                 Text="http://www.cinemateca.org/people/film_directors.htm" />
        <Button Grid.Row="6" Grid.Column="2" x:Name="btnEvenMore" Content="_Even More"
                Height="30" Margin="3" Click="btnEvenMore_Click" />

        <Label Grid.Row="7" Content="_Documentary Makers:" />
        <TextBox Grid.Row="7" Grid.Column="1" x:Name="txtDocumentaryMakers" 
                Text="http://en.wikipedia.org/wiki/List_of_directors_and_producers_of_documentaries" />
        <Button Grid.Row="7" Grid.Column="2" x:Name="btnGetDocumentaryMakers" Height="30" Margin="3"
                Content="Documentary Makers" Click="btnGetDocumentaryMakers_Click" />

        <Label Grid.Row="8" Content="_Answer:" Target="{Binding ElementName=txtAnswer}" />
        <TextBox Grid.Row="8" Grid.Column="1" x:Name="txtAnswer" />
        <Button Grid.Row="8" Grid.Column="2" x:Name="btnSolve" Height="30" Margin="3" 
                Width="{Binding ElementName=btnGetProducers,Path=ActualWidth}"
                VerticalAlignment="Top" Content="_Solve"
                 Click="btnSolve_Click" />
    </Grid>

    <!-- In lieu of linking to a file, I elected to draw a sun in Xaml -->
    <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!

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

Posted on Updated on

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

The challenge:

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

 

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

Visiting 2,000 Pages Using QueryString Parameters!

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

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

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

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

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

 

Fetching Pages inside a Loop

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

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

Use A Background Worker so Your Progress Bar will Update

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

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

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

Setting-Up Your Background Worker

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

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

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

Hooking-Up the BackgroundWorker Events

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

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

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

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

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

Raising the ProgressChanged Event with the Correct Parameters

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

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

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

Getting a List of Place Names

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

Solving the puzzle

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

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

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

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

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

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

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

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

        public MainWindow()
        {
            InitializeComponent();
        }

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

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

            //Launch!
            _BookFetcher.RunWorkerAsync();
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            txtAnswer.Text = answers;
        }
    }
}

Here is the XAML:

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

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

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

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

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

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

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

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>

Tuples for Convenience and Speed

Posted on Updated on

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

My Problem

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

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

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

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

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

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

A Couple of Explanatory Notes

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

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

Usage (Abusage?)

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

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

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>

Write Reusable Code With Function Pointers – Avoid Repeating Yourself!

Posted on Updated on

I’ve been aware of function pointers for a couple decades now, but they seemed abstract and, well, hard to use. I finally put them to good use in my latest project. My benefits were 1) less repeated code and 2) fixing a bug that resulted from writing (essentially) the same code 10 times, but screwing-up once. Yippee! I got to use a cool feature and make my code better too.

The Problem We Want to Solve: Repeated Code

Imagine this: your project has at least 10 grids to display data which you can also export. Your  users all love Excel because they love to analyze their data with fancy statistics.  We help the users by allowing them to export their grid data to CSV files, giving them the option to launch in Excel.  Since we are considerate coders, we export their data with a nice header section showing details like location, name, date, etc. which is above and  beyond the mere row and column data.

So, we wrote 10 custom export functions that have a lot in common. Every function asks our users the same two questions (where to save, whether to launch in Excel), and does the actual work of opening the file and saving user’s data. The only thing different btween the 10 functions is the output header. I hate repeated code (you should too!), so we want a way to consolidate the repeated parts . If only we had a way to write a universal  export function with customizable head output!

My solution:

  1. Write a universal CSV export function
  2. Which takes a function pointer as input
  3. That function pointer will control to write each individual header format

What the Heck is a ‘Function Pointer’?

If you read Microsoft documentation, you may never see my term “function pointer”; Microsoft uses the term “delegate”. Since I’m old-fashioned, I have been loosely tossing-around the term “function pointers” because it has the same purpose. Whatever you call it, .NET implements my concept by two techniques:

  1. Action<T>
  2. Func<TResult>

Core concept: you use either of the things above to create a function variable that you can pass around like any other variable. Sometimes we use the term “first class function” to refer to the idea that you can store, assign, return, etc. these variables. Question: when do you use an Action vs. a Func? Answer: if you need to return a value, use Func, otherwise Action is simpler. Both have variations to allow a lot more parameters (e.g. Action<T1,T2,T3,T4… T16>.

Sample Action<T>:

//Build an action that will write some data to a stream writer 'sw'
 Action<StreamWriter> printFunc = (sw) => {
     sw.WriteLine("Elevation Band:,{0}", _LocationName);
     sw.WriteLine("Date,Value");
     sw.WriteLine("----,-----");
     foreach (TimeSeriesPoint tsp in _ObservedValues) {
         sw.WriteLine("{0},{1}", tsp.TsDate, tsp.TsValue);
     }
 };

// Invoke the function with a stream writer we built somewhere else:
printFunc(someStreamWriter);

The sample shows how to

  1. Declare a variable “printFunc”
  2. Which takes an input parameter “sw”, a StreamWriter
  3. And uses sw to print some class-level variables, namely, ‘_LocationName’ and ‘_ObservedValues’. Don’t worry about what these variables represent, trust me, they are just data and irrelevant to the main concept
  4. Finally, executing the code is as simple as executing any other function, just type the name (in this case, ‘printFunc’) and pass the parameter

Read the rest of this entry »