File Manipulation

Puzzle June 5, 2022

Posted on

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
Find a country name containing brand names for deodorant and air freshener

Techniques Used

  • String manipulation
  • Simple file handling
Screen shot showing the solution

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 listsprivate 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.OpenText 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.

‘BAN’ starts at position 0, so ‘Glade’ should start after position 3

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.