Month: December 2017
Puzzle: Singer Name to Group Type
The Challenge
The Solution
Synopsis
Fun, easy! I was fortunate to have a file lying around with singer names, and it was easy enough to go to Wikipedia to grab a list of musical group types.
Also: woo-woo! I Almost solved it without a single bug! I was able to fix my solitary bug during a debug session, so I can honestly say it ran the first time.
Techniques Used
- Linq
- File-IO
- String-manipuation
- Just for a fun, anonymous functions (delegates)
The Code
string singerFile = @"Singers.txt"; string groupTypeFile = "MusicalGroupTypeList.txt"; //Make a function to sort the letters in a string, converting //to lower case, and removing all but letters Func<string, string> sortLetters = (g) => g .Where(c => char.IsLetter(c)) .Select(c => char.ToLower(c)) .OrderBy(c => c) .Aggregate("", (p, c) => p + c); //The dictionary key is the sorted letters; the //value is the original string var groupTypeDic = new Dictionary<string, string>(); //Open the file of group types; use it to build the dictionary using (StreamReader sr = File.OpenText(groupTypeFile)) { while (sr.Peek() != -1) { string grpType = sr.ReadLine(); string key = sortLetters(grpType); if (!groupTypeDic.ContainsKey(key)) { groupTypeDic.Add(key, grpType); } } } //Now read all the singers from the file using (StreamReader sr = File.OpenText(singerFile)) { while (sr.Peek() != -1) { string singerName = sr.ReadLine(); string[] tokens = singerName.Split(' '); int lastIndex = tokens.Length - 1; if (tokens.Length > 1 && tokens[0].Length == 3 && tokens[lastIndex].Length == 5) { //Combine the first name and /1st 2 letters of last name, //plus the last 2 letters of last name string remove3rdLetterLastName = tokens[0] + tokens[lastIndex].Substring(0, 2) + tokens[lastIndex].Substring(3); string sorted = sortLetters(remove3rdLetterLastName); //Does the dictionary contain the sorted letters? if (groupTypeDic.ContainsKey(sorted)) { //Yipee! Found a solution txtSolution.Text = $"{singerName} → {groupTypeDic[sorted]}"; } } } }
Explanation
Remember, if two words are anagrams, then their sorted letters are the same.
- “Bob Dylan” → “bobdyan” →”abbdnoy”
- “Boy Band” → “boyband” → “abbdnoy”
Encapsulate that Functionality in an Anonymous Function named ‘sortLetters’
Func<string, string> sortLetters = (g) => g .Where(c => char.IsLetter(c)) .Select(c => char.ToLower(c)) .OrderBy(c => c) .Aggregate("", (p, c) => p + c);
Here I create a function variable named ‘sortLetters’ that has one input parameter (string) and returns another string.
Inside the function, the input parameter (‘g’) is treated like a list of char, because I invoke Linq methods on it.
- The ‘where-clause’ removes any spaces or other funky chars
- The ‘select-clause’ converts ‘BobDylan’ → ‘bobdylan’
- The ‘OrderBy’ clause converts ‘bobdylan’ to ‘abbdnoy’
- The ‘Aggregate’ clause converts the result back to a string
Read all the Group Types, Hold Each in Variable ‘grpType’
using (StreamReader sr = File.OpenText(groupTypeFile)) { while (sr.Peek() != -1) { string grpType = sr.ReadLine();
Store the Group Type in a Dictionary
string key = sortLetters(grpType); if (!groupTypeDic.ContainsKey(key)) { groupTypeDic.Add(key, grpType); }
Read the Singers File, Store Each in Variable ‘singerName’
using (StreamReader sr = File.OpenText(singerFile)) { while (sr.Peek() != -1) { string singerName = sr.ReadLine();
Split the Singer Name into Parts, Proceed if Lengths are 3/5
string[] tokens = singerName.Split(' '); int lastIndex = tokens.Length - 1; if (tokens.Length > 1 && tokens[0].Length == 3 && tokens[lastIndex].Length == 5) {
- The ‘split’ function returns an array of string
Remove 3rd Letter From Singer’s Name, Sort the Letters
string remove3rdLetterLastName = tokens[0] + tokens[lastIndex].Substring(0, 2) + tokens[lastIndex].Substring(3); string sorted = sortLetters(remove3rdLetterLastName);
Here we concatenate the first name with the first 2 letters of last name and then the last 2 letters of the last name. Re-use the anonymous function to sort the letters.
If the Sorted Letters Match a Dictionary Entry, Add the Result to the TextBox
if (groupTypeDic.ContainsKey(sorted)) { //Yipee! Found a solution txtSolution.Text = $"{singerName} → {groupTypeDic[sorted]}"; }
Get the Code
You can download my code here. Note that you will need to create a (free) dropbox account to access the link.
Puzzle: US City With Only 3 Letters
The Sunday Puzzle Challenge:
The name of what well-known U.S. city, in 10 letters, contains only three different letters of the alphabet? Puzzle URL
Synopsis
Fun and Simple! You can get the census place name file from the US Census Bureau.
Techniques Used
Code to Solve the Puzzle
private void btnSolve_Click(object sender, RoutedEventArgs e) {
string cityFile =
@"C:\Users\User\Documents\Puzzles\Data\AmericanPlaces2k.txt";
using(StreamReader sr = File.OpenText(cityFile)) {
while(sr.Peek() != -1) {
string aLine = sr.ReadLine();
//File is fixed-width, city starts in column 9
string cityName = aLine.Substring(9, 30)
.TrimEnd();
//Place names end with town/city/CDP; remove it now:
if (cityName.EndsWith(" town"))
cityName = cityName.Substring(0, cityName.Length - 5);
else if (cityName.EndsWith(" CDP"))
cityName = cityName.Substring(0, cityName.Length - 4);
else if (cityName.EndsWith(" city"))
cityName = cityName.Substring(0, cityName.Length - 5);
//Remove spaces and other non-letters:
var letters = cityName.ToLower()
.Where(c => char.IsLetter(c));
//Check if the city has 10 letters and a distinct count of 3
if (letters.Count() == 10) {
int letterCount = letters.Distinct().Count();
if (letterCount == 3) {
txtSolution.Text += $"{cityName}\n";
}
}
}
}
}
Any Tricky Parts?
When you use Linq on a string variable, it treats your variable as an array of char. That is how I can, for example, execute this line of code:
var letters = cityName.ToLower().Where(c => char.IsLetter(c));
In the code above, ‘c‘, refers to a char in the city name. Note that the result above, ‘letters‘, has data type ‘IEnumerble<char>’. I.e., everything is a char when you use Linq on a string.
Download the Code
You can download the code from my DropBox account. To do so, you will need to create a free account. If you run the downloaded code, you will need to adjust the path to the city name file. I included that file in the download.