Visual Studio
Puzzle Victory – Word Synonym Pair/Remove Double-S
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
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
- Open the synonym file fore reading, using a stream reader named ‘sr’
- Each line contains synonyms, separated by a pipe character, like this:
- bloom|blossom|flower|flourish|coloration
- Within a Linq query, split every line from the file on the | character,
- Into an array,
- Name each array entry ‘word1’
- Use the Linq ‘where’ operator to select only entries in the array containing ‘ss’
- Join the first Linq query against another query
- Which we build by again splitting the line, referring to its entries as ‘word2’
- If the join operation has any match whatsoever,
- Then we have a potential solution
- But reject any answer we have already discovered, by comparing current against previous entries from a list called ‘distinctAnswers’
- 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
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.
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>
Solved! NPR Sunday Puzzle for December 22, 2013
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!
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?
Algorithm
The algorithm is straightforward; assume you have:
- A file containing filmmaker names, one per line
- A List<string> of words starting with ‘sun’, sorted
- 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 Dancing Puzzle, December 1st, 2013
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.
The heart of the solution is to
- Build a dictionary of events, again using the sorted event name as the key and the original event name as the value
- Iterate the dance list
- For each dance name, form a candidate by
- Iterating the letters
- Replacing each letter with u
- Sorting the resulting string to form a candidate
- 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
- That you have a file containing dance names named “DANCE_FILE”
- Which you have cleaned-up to exclude non-alpha characters like blanks and hyphens
- 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
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
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?
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
- Start with word 1, generate all possible combinations of the letters in the word
- Do the same thing for word 2
- 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:
- Sort the letters in word 1
- Sort the letters in word 2
- 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,
- Sort each word once
- Store the result in a dictionary
- Using the sorted letters as the entry’s key
- 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
- The aggregate method used above takes two inputs:
Code to Solve the 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
- A TextBox ‘txtSynonyms’ containing synonyms to the word ‘quarrel’
- A button labeled ‘Solve’ whose click-event does all the work
- 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!
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:
- Write a universal CSV export function
- Which takes a function pointer as input
- 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:
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
- Declare a variable “printFunc”
- Which takes an input parameter “sw”, a StreamWriter
- 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
- Finally, executing the code is as simple as executing any other function, just type the name (in this case, ‘printFunc’) and pass the parameter
- ← Previous
- 1
- 2