Click here to Skip to main content
15,890,724 members
Articles / General Programming / Algorithms
Tip/Trick

How to Unscramble Any Word

Rate me:
Please Sign up or sign in to vote.
4.82/5 (9 votes)
12 Apr 2021CPOL1 min read 15.7K   12   31
This is an unscramble class that can be used to decypher any word.
I could never find a good way to unscramble a word on the interwebs. Every algorithm was either brute-force or permutations...

Introduction

I could never find a good way to unscramble a word on the interwebs. Every algorithm was either brute-force or permutations.

The problem with brute-force is that it's a guessing game... very slow, or if you're lucky, very fast.

The problem with permutations is that once you go over 7 characters, you're using a bunch of memory.
e.g., a 12 character scrambled word has 479,001,600 configurations!

It finally dawned on me that if you sort the scrambled word and then sort the dictionary entries, then if we equate any sorted dictionary entry to our sorted scramble, then they must be a match!

There is probably some fancy machine learning algorithm that could do this, but my method works perfectly and instantly.

Using the Code

You'll want to embed your favorite dictionary into your project (for speed and portability).

There are a lot of free dictionary files out there; here's the one I used... https://github.com/dwyl/english-words.

Direct link... https://raw.githubusercontent.com/dwyl/english-words/master/words.txt.

The work-horse is the UnscrambleWord method; this will take care of loading the dictionary, filtering and then sorting the results and storing them in a List<string> object that will be returned to you from the call.

C#
class Unscramble
{
     private static bool _dictionaryLoaded = false;
     private static string _wordToUnscramble = "";
     private static int _totalEntries = 0;
     private static Dictionary<string, string> _sortedDictionary =
                                       new Dictionary<string, string>();
     private static List<string> _results = new List<string>();
     private static Stopwatch _stopwatch;

     //====================================================================================
     /** We don't really need a constructor **/
     //public Unscramble(string wordToUnscramble)
     //{
     //    _WordToUnscramble = wordToUnscramble;
     //}

     //====================================================================================
     public List<string> UnscrambleWord(string wordToUnscramble, bool useFiltering = true)
     {
         _stopwatch = Stopwatch.StartNew();

         if (string.IsNullOrEmpty(_wordToUnscramble))
         {
             _wordToUnscramble = wordToUnscramble;
         }
         else if (!_wordToUnscramble.Equals
              (wordToUnscramble, StringComparison.OrdinalIgnoreCase) && useFiltering)
         {   //If re-using the object and the word is different,
             //we'll need to reload the dictionary
             _dictionaryLoaded = false;
             _wordToUnscramble = wordToUnscramble;
             _results.Clear();
         }
         else if (_wordToUnscramble.Equals
                 (wordToUnscramble, StringComparison.OrdinalIgnoreCase))
         {
             _results.Clear(); //we should clear the results array so they don't stack
         }

         if (!_dictionaryLoaded) //the first call will be slightly slower
             LoadEmbeddedDictionary(wordToUnscramble.ToUpper(), useFiltering);

         string scrambleSorted = SortWord(wordToUnscramble);

         //var kvp = SortedDictionary.FirstOrDefault
         //(p => SortedDictionary.Comparer.Equals(p.Value, scrambledSort));
         var matchList = _sortedDictionary.Where
             (kvp => kvp.Value == scrambleSorted).Select(kvp => kvp.Key).ToList();

         if (matchList.Count > 0)
         {
             foreach (string result in matchList)
             {
                 System.Diagnostics.Debug.WriteLine($"> Match: {result}");
                 _results.Add(result);
             }

             _stopwatch.Stop();
             System.Diagnostics.Debug.WriteLine($"> Elapsed time: {_stopwatch.Elapsed}");
             return _results;
         }
         else //no matches
         {
             _stopwatch.Stop();
             _results.Clear();
             System.Diagnostics.Debug.WriteLine($"> Elapsed time: {_stopwatch.Elapsed}");
             return _results;
         }
     }

     //==================================================================================
     private static void LoadEmbeddedDictionary(string wordText, bool filter = false)
     {
         char[] delims = new char[1] { '\n' };
         string[] chunks;
         int chunkCount = 0;
         if (filter)
             chunks = global::Utility.Properties.Resources.
                                      DictionaryNums.ToUpper().Split(delims);
         else
             chunks = global::Utility.Properties.Resources.
                                      DictionaryNums.ToUpper().Split(delims);

         System.Diagnostics.Debug.WriteLine($"> Length filter: {wordText.Length}");
         _sortedDictionary.Clear();
         foreach (string str in chunks)
         {
             chunkCount++;
             if (filter)
             {
                 //we're assuming the word will have at least 3 characters...
                 //I mean would you really need this program if it was only two?
                 if ((str.Length == wordText.Length) &&
                      str.Contains(wordText.Substring(0, 1)) &&
                      str.Contains(wordText.Substring(1, 1)) &&
                      str.Contains(wordText.Substring(2, 1))) //just checking the 1st,
                                    //2nd & 3rd letter will trim our search considerably
                 {
                     try
                     {
                         _sortedDictionary.Add(str, SortWord(str));
                     }
                     catch
                     {
                         //probably a key collision, just ignore
                     }
                 }
             }
             else
             {
                 try
                 {
                     _sortedDictionary.Add(str, SortWord(str));
                 }
                 catch
                 {
                     //probably a key collision, just ignore
                 }
             }
         }
         System.Diagnostics.Debug.WriteLine($">
         Loaded {_sortedDictionary.Count} possible matches out of {chunkCount.ToString()}");
         _totalEntries = chunkCount;
         _dictionaryLoaded = true;
     }

     //=================================================================================
     private static string SortWord(string str)
     {
         return String.Concat(str.OrderBy(c => c));

         /*** Character Array Method ***
         return String.Concat(str.OrderBy(c => c).ToArray());
         *******************************/

         /*** Traditional Method ***
         char[] chars = input.ToArray();
         Array.Sort(chars);
         return new string(chars);
         ***************************/
     }

     #region [Helper Methods]
     //=================================================================================
     public TimeSpan GetMatchTime()
     {
        return _stopwatch.Elapsed;
     }

     //=================================================================================
     public List<string> GetMatchResults()
     {
        return _results;
     }

     //=================================================================================
     public int GetMatchCount()
     {
        return _results.Count;
     }

     //=================================================================================
     public int GetFilterCount()
     {
        return _sortedDictionary.Count;
     }

     //=================================================================================
     public int GetDictionaryCount()
     {
        return _totalEntries;
     }
     #endregion
}

Testing/Implementation

To drive the code, you would do this...

C#
string scrambled = "mctmouicnaino";
Unscramble obj1 = new Unscramble();
List<string> results = obj1.UnscrambleWord(scrambled);
if (results.Count > 0)
{
    Console.WriteLine($"> Total matches: {obj1.GetMatchCount()}");
    foreach (string str in results)
    {
        Console.WriteLine($">> {str}");
    }
    Console.WriteLine($"> Total time: {obj1.GetMatchTime()}");
    Console.WriteLine($"> Filtered set: {obj1.GetFilterCount()}
                          out of {obj1.GetDictionaryCount()}");
}
else
{
    Console.WriteLine("> No matches available:
            Check your spelling, or the dictionary may be missing this word.");
}

In the class, we could add some more LINQ methods to change the order, take the top result, etc., but this should be a good remedy for any unscramble engine base.

History

  • 11th April, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
AnswerRe: How to Unscramble Any Word Pin
largenqcd16-Apr-21 6:46
largenqcd16-Apr-21 6:46 
GeneralMy vote of 5 Pin
rspercy6513-Apr-21 8:32
rspercy6513-Apr-21 8:32 
GeneralRe: My vote of 5 Pin
GuildOfCalamity15-Apr-21 15:03
GuildOfCalamity15-Apr-21 15:03 
GeneralAnagrams and suggestions in VB Pin
HenkAlles18-Apr-21 23:54
HenkAlles18-Apr-21 23:54 
QuestionAn alternate algorithm Pin
Member 1122079213-Apr-21 8:31
Member 1122079213-Apr-21 8:31 
QuestionUnscramble Pin
johnta113-Apr-21 7:18
professionaljohnta113-Apr-21 7:18 
QuestionAn similar alternative Pin
Rich Leyshon13-Apr-21 1:57
Rich Leyshon13-Apr-21 1:57 
AnswerRe: An similar alternative Pin
John Brett13-Apr-21 2:23
John Brett13-Apr-21 2:23 
There is mileage in this approach.
Conventional hashing algorithms for strings are intended to differentiate words with characters exchanged, however combining the OP's SortWord method and then hashing that gives us a hash where original string character order is unimportant.

Taking this a step further, the OP creates a dictionary of original string to sorted string, and then searches it linearly. Of course, dictionaries are optimised for key lookup (by creating a hash of the key), so a small change to the algorithm (storing the sorted word as the key, and a list of possible match words as the value) and we can just go straight to the answer via a dictionary lookup. Which should be considerably faster on lookup.
GeneralRe: An similar alternative Pin
GuildOfCalamity16-Apr-21 5:34
GuildOfCalamity16-Apr-21 5:34 
QuestionA good algorithm Pin
vit$oft13-Apr-21 1:48
vit$oft13-Apr-21 1:48 
AnswerRe: A good algorithm Pin
GuildOfCalamity15-Apr-21 15:04
GuildOfCalamity15-Apr-21 15:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.