File Manipulation
Puzzle June 5, 2022
Country Name Containing a Deodorant and an Air Freshener
This week’s challenge comes from listener Ben Bass of Chicago. The name of what country contains a deodorant and an air freshener in consecutive letters?
Link to the challenge
Techniques Used
- String manipulation
- Simple file handling
Synopsis
Another super easy one! There aren’t that many consumer products to search for, and for some reason, people have made web pages for those products. So I was able to copy the product names manually off those web pages and save as a pair of files I used to solve the puzzle. Happily, I already had a list of country names, so no work was required for that.
After making my files, the process involved 1) reading the country, deodorant and freshener names into three lists, 2) iterating the country list, 3) for each country, searching the name for a deodorant, 4) if a hit resulted, searching again for a freshener.
Since Will didn’t say whether the deodorant was first, I actually needed two search sections, one that searches for deodorant being first in the country, the other for the freshener being first.
The similarities in loading the three lists resulted in consolidating my code to a method to do the work. Similarly, I wrote a single method to search a country name for the contents of two anonymous lists, thereby allowing me to invoke the same code twice but with the lists in different order. The result was another consolidation of code, again making the resulting program shorter and easier to understand.
Here’s the Code!
First, the Main Method, Which Makes use of My Two Methods
private void btnSolve_Click(object sender, RoutedEventArgs e) { //1) Make a list of country names: var fName = @"C:\Users\Randy\Documents\Puzzles\Data\Countries.txt"; var countryList = ReadFileIntoList(fName); //2) Make another list of deodorant names: fName = @"C:\Users\Randy\Documents\Puzzles\Data\Deodorants.txt"; var deodorantList = ReadFileIntoList(fName); //3) And now another list of air fresheners: fName = @"C:\Users\Randy\Documents\Puzzles\Data\AirFresheners.txt"; var freshenerList = ReadFileIntoList(fName); //4) Loop through the countries and check each to see if it matches: foreach (var country in countryList) { //This block of code checks for deodorant first, followed by air freshener var solution = FindListEntriesInCountryName(country, deodorantList, freshenerList); if (!string.IsNullOrWhiteSpace(solution)) txtResult.Text += solution; //5) This block of code swaps the list order to check for //air freshener first, followed by deodorant: solution = FindListEntriesInCountryName(country, freshenerList, deodorantList); if (!string.IsNullOrWhiteSpace(solution)) txtResult.Text += solution; } }
Remarks
The code above is pretty straightforward. Code blocks marked above as items 1, 2, 3 serve to load the three files into three lists. Each returned list contains string entries
The code blocks marked as items 4 and 5 above serve to search the country name for deodorant name and air freshener. The parameter order is swapped when I invoke ‘FindListEntriesInCountryName
‘, meaning that block 4 searches for deodorant followed by an entry in the freshener list, and block 5 does vice versa.
My Two Methods
/// Open a file, read each line and add it to a list private List ReadFileIntoList(string fName) { var result = new List<string>(); using (var sr = File.OpenText(fName)) while (sr.Peek() != -1) { var addMe = sr.ReadLine() ?? ""; result.Add(addMe); } return result; }/// Searches a country name using entries from two lists private string FindListEntriesInCountryName(string country, List<string> list1, List<string> list2) { var result = ""; foreach (var deodorant in list1) { var p1 = country.IndexOf(deodorant, StringComparison.CurrentCultureIgnoreCase); if (p1 >= 0) { var startPos = p1 + deodorant.Length; if (startPos < country.Length) foreach (var freshener in list2) { var p2 = country.IndexOf(freshener, startPos, StringComparison.CurrentCultureIgnoreCase); if (p2 >= 0) { result = $"{country} - {deodorant} - {freshener}\n"; break; } } } } return result; }
Remarks on the Two Methods
ReadFileIntoList
The first method opens the file whose name was provided as a parameter, ‘fName
‘. File.OpenTex
t gives us a StreamReader
which has two methods we use: Peek
and ReadLine
.
var addMe = sr.ReadLine() ?? "";
What’s that weird looking code above? Note that ReadLine might theoretically return null; I deal with that issue by using the ??
operator (the null coalescing operator). The string I supply after the ??
operator (an empty string) is substituted whenever a null is returned. I could have substituted “WTF” instead of an empty string, but that would have been silly 😆.
FindListEntriesInCountryName
The second method takes a country name as input, plus two lists named ‘List1’ and ‘List2’. First we iterate list 1, using the IndexOf
method to check if a list entry is present in the list. IndexOf
will return a negative number if it fails, so when we find a number 0 or higher, we know we have a hit.
In that case, we check if the second list might contain another match. It must start after the first hit, so I use a variable ‘startPos
‘ to indicate where we should look inside the country name. For example, if I found deodorant ‘DogBreath’ at position 2 in the country name, then startPos
would be 11, because the length of the deodorant is 9 and 9 + 2 = 11.
In actuality, ‘Ban’ is at position 0, so I look for entries in the freshener list starting at position 3. Remember that string indices start at position 0, not position 1.
Can you Spot my Cheat?
If you’re paying close attention, you might realize that Will says the 2nd entry should start immediately after the first. But my code allows intervening letters. I left this in place for simplicity and because I found a single solution. But theoretically, my code could have found ‘BanBlahBlahBlahGladeSh as an answer!
Download my Code
Click here to download my code; note that you will need a (free) DropBox account to do so.