Month: May 2022

Sunday Puzzle May 22, 2022

Posted on Updated on

Manipulate an Island Name to get Another Island

It’s an easyish one and comes from Blaine Deal, who conducts a weekly blog about Will’s NPR puzzles. Take the name of an island. Move its first letter two spaces later in the alphabet (so A would become C, B would become D, etc.). Reverse the result and you’ll have the name of another island. What islands are these?

Link to the challenge
Change the first letter and reverse the name to get another island

Techniques Used

  • String manipulation
  • Simple file handling
  • Linq methods ‘Aggregate’ and ‘Reverse’
Screen shot showing the solution

Synopsis

Will was right, that was a pretty easy puzzle 😊. The hardest part about this one was building my island list. Eventually, I was able to use the list on Britannica.com, after manually editing their text for about half an hour.

After saving the result as a file, it was a simple matter of 1) reading that file into a list, 2) manipulating each list entry, and 3) checking whether the result was also an entry in that list.

Here’s the Code!

private void btnSolve_Click(object sender, RoutedEventArgs e) {
  Mouse.OverrideCursor = Cursors.Wait;
  var islandLst = new List<string>();
  //Read the file and put each line into the list declared above:
  var fName = @"C:\Users\Randy\Documents\Puzzles\Data\Islands.txt";
  using (var sr = File.OpenText(fName))
    while (sr.Peek() != -1)
      islandLst.Add(sr.ReadLine() ?? "");

  foreach(var island in islandLst) {
    //Manipulate the first letter and store the result in variable 'switched'
    var switched = (char)(char.ToLower(island[0]) + 2) + island[1..];
    //Reverse the letters and convert to a string:
    var reversed = switched.Reverse().Aggregate("", (p,c) => p + c);
    //Convert the first letter to upper case
    var candidate = char.ToUpper(reversed[0]) + reversed[1..];
    //Search the list for the result:
    var ndx = islandLst.BinarySearch(candidate);
    if (ndx >= 0)
      //Display the results:
      txtResult.Text += $"{island} - {islandLst[ndx]}\n";
  }

  Mouse.OverrideCursor = null;
}

Discussion

Here’s how I manipulate each entry in the islandLst to form a candidate to check. The lines highlighted above are discussed below:

  • var switched = (char)(char.ToLower(island[0]) + 2) + island[1..];
    • Note that .NET allows you to treat a string variable as an array of char, so you can use indexing on that array. To wit,
      • island[0] retrieves the first character from that array, at index 0
    • .NET also allows you to perform arithmetic on char variables, so, if c is a char variable, then c + 2 is a character moved up in the alphabet by two. So 'g' + 2 = 'i', because i is two higher than g. However, the result is not a char, so I need to cast it back to char before concatenating it with the remainder of the string. Casting is accomplished with the syntax (char), used as a prefix above.
  • var reversed = switched.Reverse().Aggregate("", (p,c) => p + c);
    • Reverse()’ is a Linq method that builds an IEnumerable ‘list’ of char in the reverse order of the original. I can use it on a string because, as mentioned above, a string is an array of char, and Linq can operate on an array (because an array is an IEnumerable)
    • Aggregate‘ is another Linq method that has two parameters:
      • The initial/seed value to aggregate with, namely, an empty string “”
      • A Lambda expression (p,c) => p + c that tells .NET how to perform the aggregation. My variable ‘p‘ represents the previous value (the results so far), and my variable ‘c‘ represents the current list entry being processed. Effectively, my code says “for each list entry, add it to the running output”
    • Why use Aggregate? Because, as mentioned above, Reverse gives me a list (technically an IEnumerable), and I need to convert it to a string.
  • var candidate = char.ToUpper(reversed[0]) + reversed[1..];
    1. Take the first char (reversed[0]) and convert it to upper case (char.ToUpper)
    2. Concatenate that with the remainder of the string (reversed[1..]). Note that the plus sign + serves as a concatenation operator, and .NET allows you to concatenate a char to a string.

Finally, having built a candidate, I search the list again to see if that list actually contains the candidate.

var ndx = islandLst.BinarySearch(candidate);

If the binary search operation returns a non-negative number, it means we have a solution!

Get the Code

Here’s the link to get my code. Note that you will need a (free) DropBox account to access that link. The compressed file contains my island list, which you can use to solve the puzzle yourself.

Sunday Puzzle for May 15, 2022

Posted on Updated on

Manipulate an Actor Name to get a French Phrase

This week’s challenge comes from listener John Sieger, of Wauwatosa, Wis. Name a famous living movie star. Insert an R in the middle of the first name, and drop the last two letters of the last name. You’ll get a familiar French phrase. What is it?

Link to the challenge
Add ‘r’ to the middle of an actor’s first name, remove the last two letters from their last name to get a French phrase

Techniques Used

  • File handling
  • Text processing, including manipulation of Unicode characters
  • Screen scraping with HtmlAgilityPack
  • XPath
  • String manipulation
  • Simple async operations
Screen shot showing the solution

Synopsis

Note: I originally published this a few days ago as a Page; I should have published it as a Post, because, for whatever reason, WordPress makes it really hard to find pages. So I have republished it hear as a page.

This one was a bit harder than normal; after struggling with my original actor list, I had to augment it, and finding lists of French phrases was hard. In interests of full disclosure, I had to try a few things that aren’t shown in my published code to get it to work. Specifically, I had to convert the actor names into French words, then inspect the output and check if any looked like a French phrase, because “carte blanche” wasn’t in any list I found. After I inspected my output, I added carte blanche to my list of phrases.

But the good news is now I get to show off some elementary usage of the outstanding HtmlAgilityPack library, which allows you to process web pages using XPath, making it possible to find text on a page that matches certain HTML markup patterns. In this case, links (a elements) which are children of h3 header elements having a particular class attribute.

For language choice, I once again used .NET/WPF, one reason being I am pretty good at screen scraping in .NET, also because I know how to remove diacritical marks from text using .NET. I had to screen-scrape the IMDB site to get more actors, and the french phrases contain lots of non-ASCII characters (i.e. diacritical marks) that I needed to compare against the manipulated actor names. I’m better at both those tasks in .NET compared to Python.

Here’s the Code!

First, Code That Solves the Puzzle

private void btnSolve_Click(object sender, RoutedEventArgs e) {
  Mouse.OverrideCursor = Cursors.Wait;
  var fName = @"C:\Users\Randy\Documents\Puzzles\Data\Actors\FrenchPhrases.txt";
  var phraseLst = new List<string>();

  //Open the French Phrases file, read each line, 
  //remove diacritics, add to list 'phraseList'
  using (var sr = new StreamReader(File.OpenRead(fName))) {
    while (sr.Peek() != -1) {
      var aLine = (sr.ReadLine() ?? "").ToLower();
      int p = aLine.IndexOf(":");
      var phrase = aLine[..p];

      //The method 'RemoveDiacritics' is listed below
      phraseLst.Add(RemoveDiacritics(phrase));
    }
  }

//Now process the actors file; manipulate each name and see if
//the result matches an entry on our list 'phraseList'
  var solutionCount = 0;
  fName = @"C:\Users\Randy\Documents\Puzzles\Data\Actors\Actors.txt";
  using (var sr = new StreamReader(File.OpenRead(fName))) {
    while (sr.Peek() != -1) {
      var aLine = (sr.ReadLine() ?? "").ToLower(); ;
      var tokens = aLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
      if (tokens.Length > 1) {
        //Find the middle index of the first name
        var p = (int)tokens[0].Length / 2;
        //Inject an 'r' at the middle index
        var first = tokens[0][..p] + "r" + tokens[0][(p)..];

        //Check if the last name has enough letters to trim the last two
        if (tokens[^1].Length > 3) {
          //Remove the last 2 letters:
          var last = tokens[^1][0..^2];

          var newName = $"{first} {last}";
          //Check if the transformed name is in the list:
          var ndx = phraseLst.IndexOf(newName);

          //Display the result(s)
          if (ndx >= 0) {
            txtResults.Text += $"{aLine} - {phraseLst[ndx]}\n";
            solutionCount++;
          }
        }
      }
    }
  }
  if (solutionCount > 0)
    txtMessage.Text = $"Found {solutionCount} matche(s)!";

  Mouse.OverrideCursor = null;
}

Code Highlights

  • Using standard file handling techniques, open the file containing the phrases
  • Each line in that file looks like the following example:carte blanche: Unrestricted power to act at one’s own discretion
    • carte blanche: Unrestricted power to act at one’s own discretion
  • The colon character separates the phrase from its translation
  • The following code extracts the characters to the left of the colon:
    • int p = aLine.IndexOf(":");
    • var phrase = aLine[..p];
  • First, we find the position p of the colon
  • Then we extract the characters starting at the beginning of the line, up to position p
  • The result is added to a list phraseLst, after removing any accents, umlauts, or other glyphs that aren’t present in English spelling
  • Next, we process the actor file and manipulate each name
  • To separate the name into individual words, I use the split method to get the array tokens
  • The first entry in tokens is the first name; find the middle position by dividing length by 2, and insert ‘r’ at that position
    • var p = (int)tokens[0].Length / 2;
    • var first = tokens[0][..p] + "r" + tokens[0][(p)..];
  • Now build the manipulated last name:
    • var last = tokens[^1][0..^2];
    • Note that tokens[^1] extracts the entry counting backwards from the end, i.e. the last entry
    • The notation [0..^2] means to take the letters starting at position 0, up to (exclusive) the 2nd from the end letter
  • Finally, we check if the manipulated name is in our list, and if so, display it:
    • var newName = $"{first} {last}";
    • var ndx = phraseLst.IndexOf(newName);

Removing Diacritical Marks

static string RemoveDiacritics(string text) {
  var normalizedString = text.Normalize(NormalizationForm.FormD);
  var stringBuilder = new StringBuilder(capacity: normalizedString.Length);

  for (int i = 0; i < normalizedString.Length; i++) {
    char c = normalizedString[i];
    var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
    if (unicodeCategory != UnicodeCategory.NonSpacingMark) {
      stringBuilder.Append(c);
    }
  }

  return stringBuilder
    .ToString()
    .Normalize(NormalizationForm.FormC);
}

For the sake of strict honesty, I copied this code from a post on Stack Overflow, so I’m not an expert on what it does. Note that the key method invoked is the ‘Normalize‘ method, which converts to a particular form of Unicode.

Note that FormD “Indicates that a Unicode string is normalized using full canonical decomposition, followed by the replacement of sequences with their primary composites, if possible.”

Code to Get More Actor Names

private async void btnGetMoreActors_Click(object sender, RoutedEventArgs e) {
  Mouse.OverrideCursor = Cursors.Wait;

  //Load the existing text file containing actors, into a list 'actorList'
  var fName = @"C:\Users\Randy\Documents\Puzzles\Data\Actors\Actors.txt";
  var actorList = new List<string>();
  using (var sr = new StreamReader(File.OpenRead(fName))) {
    while (sr.Peek() != -1) {
      var aLine = (sr.ReadLine() ?? "");
      actorList.Add(aLine);
    }
  }
  var addedCount = 0;

  //By running the following code asynchronously, the UI remains responsive
  //and we can update the progress bar
  await Task.Run(async () => {
    //IMDB has 10 pages of 100 actors each page, loop through them
    var baseUrl = 
 @"https://www.imdb.com/list/ls058011111/?sort=list_order,asc&mode=detail&page={0}";
    for (var i = 1; i <= 10; i++) {
      //The url takes a page number parameter; inject it into the base url:
      var url = string.Format(baseUrl, i);
      var allText = "";
      using (var cl = new HttpClient())
        allText = await cl.GetStringAsync(url);

      //Make an instance of the main object in the HtmlAgilityPack library 
      var hDoc = new HtmlDocument();
      hDoc.LoadHtml(allText);

      //Use XPath to find a list of nodes which are
      // 1) children of h3 elements
      // 2) if said h3 element has class 'lister-item-header
      // 3) and if said child is an 'a' element (anchor, i.e. link)
      var actorLinks = 
         hDoc.DocumentNode.SelectNodes("//h3[@class='lister-item-header']/a");
      //There should be 100 matches, we will grab the 'InnerText' of each
      foreach (var anchor in actorLinks) {
        //Clean-up the actor name and check if it is in the existing list:
        var actor = anchor.InnerText.Replace("\\n", "").Trim();
        var ndx = actorList.BinarySearch(actor);

        //we found a new actor when ndx < 0
        if (ndx < 0) {
          Console.WriteLine(actor);
          //Insert the actor at the correct alphabetical index:
          actorList.Insert(~ndx, actor);
          Dispatcher.Invoke(() => txtMessage.Text = $"Added {actor}");
          addedCount++;
        }
      }

      //Update progress bar
      Dispatcher.Invoke(() => prg.Value = 100F * (float)i / 10F);
    }
  });

  txtMessage.Text = $"Added {addedCount} actors; writing file";
  if (addedCount > 0) {
    //Update the file with new entries
    using(StreamWriter sw = new StreamWriter(File.Create(fName))) {
      foreach (var actor in actorList)
        sw.WriteLine(actor);
    }
  }
  txtMessage.Text = "Done!";

  Mouse.OverrideCursor = null;
}

As you might guess, I had to get more actors because my original list was deficient. I got my original list from Wikipedia, which sometimes has some challenges organizing lists. Happily, IMDB has an enhanced list which I added to my file.

Code Highlights

  • First, we read the existing file and build a list ‘actorList’, using standard techniques
  • Note that IMDB has 10 pages of actor names; each page can be retrieved by setting a URL parameter named ”page”
  • For example, the 3rd age can be retrieved with this url:
  • So, my code loops from page 1 to page 10, each pass fetching the next page by number
  • The following two lines read the page into a string variable, using the URL created above:
    • using (var cl = new HttpClient())
    • allText = await cl.GetStringAsync(url);
  • Next, we load the text into the HtmlAgilityPack main object, the HtmlDocument:
    • var hDoc = new HtmlDocument();
    • hDoc.LoadHtml(allText);
  • Now we can use XPath to find the nodes containing the actor names:
    • var actorLinks = hDoc.DocumentNode.SelectNodes("//h3[@class='lister-item-header']/a");
    • The XPath code effectively says to
      • Find h3 elements
      • Having class ‘lister-item-header‘ (because this is the name of the CSS class that IMDB created to decorate these elements with; I know this because I inspected the page source)
      • Having found such an h3 element, selecting the a child node (‘a‘ stands for ‘anchor’, i.e. a link)
  • Next, we iterate each entry in the IEnumerable actorLinks‘; for each, we extract the InnerText and process it.

Get the Code!

Here’s a link to my code on DropBox; note that you will need a (free) DropBox account to access it.

Sunday Puzzle, May 1, 2022

Posted on Updated on

Manipulate a Number Name to get Another number

This week’s challenge is more challenging than last week’s. Write down the name of a number. Move each letter four spots later in the alphabet — so A would become E, B would become F, etc. The result will be a number that’s 44 more than your first one. What numbers are these?

Link to the challenge
Adding 4 to each letter in the number names “one”, “two”, “three”

Discussion

This time I decided to create a WPF solution using Visual Studio. Manipulating the number names was pretty easy, but finding the correct inputs was a lot harder. I had to think of quite a few different ways of representing number names, including Spanish, German, leet and Roman Numerals. Thankfully, I found the solution before I had to resort to Pig-Latin!

This time, instead of explaining the code in text, I made a short movie of the code in action, in a debug session. The reason being that the movie should be easier to understand, in part because I can show the values of variables and demonstrate little blocks of code in the Immediate Pane.

Techniques Used

  • File Handling
  • String manipulation
  • Binary Search
  • Sorting lists in .NET
  • Simple classes
  • StringBuilder
Screen shot showing the solution. Interpretation: when you start with ‘three‘ and move each letter 4 spaces in the alphabet, you arrive at ‘xlvii‘, i.e. the Roman Numeral expression of 47. The difference between 47 and 3 is 44, i.e. this is the solution to the puzzle!

Link to the Video Clip

Hmm, it seems I have to post a link here (instead of directly embedding my video) because I don’t have a Pro account. Here’s my little video! Note that you’ll need a free DropBox account to download it. I hope you learn a couple tricks about using the Visual Studio debugger from watching my video.

Download my Code

Here’s a link to my code. Again, you’ll need a free DropBox account to retrieve that link. My data file of number-names is included in that zip file; the file name is “NumberNames.txt”.

Additional Comments

  • Writing the code to solve the puzzle was pretty easy. The hard part was figuring out all the different ways to represent number names. I’ll step out on a limb and predict that very few people will send a solution to Will this week.
  • One thing I didn’t address in my video is why I bothered to use a Binary search to solve the puzzle, after all, the list of number-names is so short that the speed increase is almost undetectable. In case you wondered why, the answer is:
    1. I’m familiar enough with this technique that it only took me a minute to set up the IComparer class. Compared to Python, binary searches are easy in .NET!
    2. My default is to write code to be efficient, unless doing so makes it hard to read, and Binary Search is quite efficient
    3. I thought someone might be interested to learn how to do this in .NET

Here’s My Code

Since I can’t embed my video directly, here’s the code. First is the “code-behind” for the main from. Below that, I will list my little class to hold instances of NumberNames.

using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Input;

namespace Puzzle2022_05_01 {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        /// This method fires when user clicks the 'Solve' button.
        /// 1) Loads a file of number names,
        /// 2) Manipulates each name according to the puzzle instructions. 
        /// 3) If the manipulation results in a
        /// match against another number name, then compute the difference in
        /// the numbers to see if it meets the requirements.
        private void btnSolve_Click(object sender, RoutedEventArgs e) {
            Mouse.OverrideCursor = Cursors.Wait;
            btnSolve.IsEnabled = false;

            var fName = @"C:\Users\User\Documents\Puzzles\NumberNames.txt";
            //1) Build a list 'numberLst' from the number names file:
            var numberLst = LoadNumberNamesFromFile(fName);

            //This object controls which property (field) is used to perform
            //the comparison, namely, the 'Name' property
            var comp = new NumberNameComp();
            //Sorting is necessary to enable the binary search below:
            numberLst.Sort(comp);

            //This list will be bound to the the datagrid which users views
            var displayLst = new List();
            //Now iterate the list, manipulate each name, and
            //search the list for the manipulation result
            foreach(var aNumber in numberLst) {
                //2) Manipulate the original number name:
                var manipulatedName = ManipulateName(aNumber.Name);

                //3) Search the list for an entry matching manipulated name whose
                // difference is 44:
                if (MeetsCriteria(manipulatedName, aNumber.Number, numberLst)) {
                    //Build something to display in the grid:
                    var display = new NumberName {
                        Name = manipulatedName,
                        Orig = aNumber.Name,
                        Difference = 44
                    };
                    displayLst.Add(display);
                }
            }

            lblAnswer.Visibility = Visibility.Visible;
            grdResults.ItemsSource = displayLst;
            grdResults.Visibility = Visibility.Visible;
            Mouse.OverrideCursor = null;
        }

        /// This method opens the file, reads each line, converts the
        /// contents into a NumberName object, and inserts it
        /// into a list to return
        private List LoadNumberNamesFromFile(string fName) {
            var result = new List();
            using (var sr = new StreamReader(File.OpenRead(fName))) {
                while (sr.Peek() != -1) {
                    var aLine = sr.ReadLine();
                    var p = aLine.IndexOf("=");
                    var addMe = new NumberName {
                        Number = long.Parse(aLine.Substring(0, p - 1)),
                        Name = aLine.Substring(p + 2).ToLower()
                    };
                    result.Add(addMe);
                }
            }

            return result;
        }

        /// This method manipulates the name by adding 4 to each letter,
        /// except for hyphens and spaces
        /// For example, 'three' or 'thirteen'
        /// The manipulated name, such as 'xlvii'
        private string ManipulateName(string theName) {
            var result = new StringBuilder();
            foreach (var ltr in theName) {
                //Add 4 unless the character is hyphen or space
                char nextLtr = (ltr == '-' || ltr == ' ')
                    ? ltr
                    : (char)(ltr + 4);
                result.Append(nextLtr);
            }

            return result.ToString();
        }

        /// This method returns true if the manipulated name matches an entry the list,
        /// and if the difference is 44        private bool MeetsCriteria(string manipulatedName, long origNumber, List numberLst) {
            bool result = false;
            var comp = new NumberNameComp();
            var findMe = new NumberName { Name = manipulatedName };
            //3) Searche the
            var ndx = numberLst.BinarySearch(findMe, comp);
            if (ndx >= 0) {
                //If binarysearch succeded, ndx will be non-negative,
                //so compute the difference
                var diff = numberLst[ndx].Number - origNumber;

                //The puzzle criteria demands a difference of 44, this entry 
                //is the solution
                if (diff == 44)
                    result = true;
            }

            return result;
        }
    }
}
    public class NumberName {
        /// <summary>The numeric value of the number, used to compute difference</summary>
        public long Number { get; set; }

        /// <summary>The transformed name of the number, such as xvlii</summary>
        public string Name { get; set; }

        /// <summary>The original number name, such sa 'three'</summary>
        public string Orig { get; set; }

        /// <summary>Holds the difference between the original number and the transformed number</summary>
        public long Difference { get; set; }

        /// <summary>
        /// Displays the number, particularly when using the debugger
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            return $"{Number}, {Name}";
        }
    }

    /// <summary>
    /// A class dedicated to comparing two instances of the NumberName class
    /// by their name
    /// </summary>
    /// <remarks>
    /// An instance of this class is used for sorting the a list of NumberName
    /// instances, and for performing BinarySearch on such a list
    /// </remarks>
    public class NumberNameComp : IComparer<NumberName> {
        public int Compare(NumberName x, NumberName y) {
            return string.Compare(x.Name, y.Name);
        }
    }