GitList
Repositories
Help
Report an Issue
boggleNet
Code
Commits
Branches
Tags
Search
Tree:
248fba3
Branches
Tags
master
boggleNet
BoggleList.cs
Revert "Removed an old artifact that was no longer used."
Dev Ghai
commited
248fba3
at 2013-12-25 06:53:30
BoggleList.cs
Blame
History
Raw
using System; using System.Collections.Generic; using System.IO; namespace Boggle { /// <summary> /// Supported lists. /// </summary> public enum BoggleLists { /// <summary> /// Scrabble dictionary for USA, Canada and Thailand /// </summary> TWS, /// <summary> /// Scrabble dictionary used world wide except in USA, Canada and Thailand. /// </summary> //SOWPODS, /// <summary> /// A huge dictionary found on official Internet Scrabble Club's website (http://www.isc.ro/en/commands/lists.html) /// </summary> ZINGARELLI } public class BoggleList { /// <summary> /// Contains a hybrid tree that has been optimized for recursive search. /// </summary> private Dictionary<string, ListWord> _WordList = new Dictionary<string, ListWord>(27); //top level will store only 3 letter words or substrings. Declaring proper size makes insertion operation O(1) public Dictionary<string, ListWord> Wordlist { get { return _WordList; } } /// <summary> /// Contains a list of dictionaries that have been loaded, indexed by enum that contains dictionaries we support. /// </summary> private Dictionary<BoggleLists, Dictionary<string, ListWord>> _LoadedDictionaries; /// <summary> /// Determines if we have an absolute path /// </summary> /// <param name="filePath">File that contains the list SORTED list of words. Path can be relative or absolute.</param> /// <returns>The absolute file path that we can use to access it.</returns> private string GetAbsoluteFilePath(string filePath) { //check if the absolute file path exists. if (!File.Exists(filePath)) { //try checking if this is a relative file path and prepend current directory's path. filePath = string.Format("{0}\\{1}", Directory.GetCurrentDirectory(), filePath); if (!File.Exists(filePath)) { filePath = string.Empty; } } return filePath; } /// <summary> /// Curates the list for use by this solver. Basically culls out words that do not meet the criteria. /// Criteria: Min_word_length < word_length < Max_word_length. Max_word_length = sideLength^2 + 1. /// </summary> /// <param name="filePath"> Path to file that contains the SORTED list of words. /// It can be relative to current execution directory or absolute.</param> /// <param name="boardSideLength">Length of the side of board in number of tiles.</param> /// <param name="minWordLength">Number of alphabets that can be there in the shortest word on the board.</param> private void CurateList(string filePath, int boardSideLength, int minWordLength) { //filePath = GetAbsoluteFilePath(filePath); //true tells stream reader to auto detect encoding of the file. StreamReader rawList = new StreamReader(filePath, true); //Create a new file. Do not append anything. string curatedFilePath = string.Format("{0}.{1}.{2}", filePath, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString()); StreamWriter curatedList = new StreamWriter(curatedFilePath, false); string word; //write out the words whose number of characters are between the shortest and largest provided length. while ((word = rawList.ReadLine()) != null) { if (word.Length >= minWordLength && word.Length <= boardSideLength * boardSideLength + 1) { curatedList.WriteLine(word); } } curatedList.Flush(); curatedList.Close(); rawList.Close(); } public BoggleList() { //Initialize the dictionary containing the lists. _LoadedDictionaries = new Dictionary<BoggleLists, Dictionary<string, ListWord>>(Enum.GetNames(typeof(BoggleLists)).Length); } /// <summary> /// Loads the list depending on the selected dictionary. /// </summary> /// <param name="dict">Type of dictionary to load.</param> /// <param name="boardSideLength"></param> /// <param name="minWordLength"></param> /// <returns>A hybird tree containing the words with each level containing one alphabet (or QU)</returns> public Dictionary<string, ListWord> LoadList(BoggleLists dict, int boardSideLength, int minWordLength, string listRootPath) { if (_LoadedDictionaries.ContainsKey(dict)) return _LoadedDictionaries[dict]; string filePath = string.Empty; if (NeedsCuration(dict, minWordLength, listRootPath, ref filePath)) CurateList(filePath, boardSideLength, minWordLength); _WordList = new Dictionary<string, ListWord>(27); //true tells stream reader to auto detect encoding of the file. StreamReader rawList = new StreamReader(filePath, true); string word, substring; ListWord prevListWord; int startIndex; while ((word = rawList.ReadLine()) != null) { //First deal with the head node and then deal with following letters. startIndex = 0; word = word.ToUpper(); substring = word.Substring(0, 1); //if the string starts with Q and there is a 'U' following it, then put QU on tile and increment starting index accordingly. if (substring == "Q" && word[1] == 'U') { substring += "U"; startIndex++; } //If there is no space allocated for this letter at root level, allocate some. if (!_WordList.ContainsKey(substring)) { _WordList.Add(substring, new ListWord()); } prevListWord = _WordList[substring]; //fit the word in the tree structure for (int i = startIndex + 1; i < word.Length; i++) { //We are dealing with one letter at each level substring = word.Substring(i, 1); //But if the letter is Q and there is a U following it, then treat this as one letter. if (substring == "Q" && i + 1 < word.Length && word[i + 1] == 'U') { substring += "U"; //also increment the index as we have consumed the next letter. i++; } //Has this combination of letters appeared before? if (prevListWord.Next == null) { //if not, then make some space to store this combination prevListWord.Next = new Dictionary<string, ListWord>(27); //and store it. prevListWord.Next.Add(substring, new ListWord()); } //if it has, else if (!prevListWord.Next.ContainsKey(substring)) { //then just store it. prevListWord.Next.Add(substring, new ListWord()); } //Update the dictionary to point to current substring's last character/entry in the tree. prevListWord = prevListWord.Next[substring]; } //set the value of the leaf node to be true to point that we have completed the word. prevListWord.IsWord = true; } //Store it in the collection _LoadedDictionaries.Add(dict, _WordList); return _WordList; } /// <summary> /// Checks to see if the proper file exists that contains the appropriate words. /// </summary> /// <param name="list">Type of list to check for.</param> /// <param name="minWordLength">Minimum number of LETTERS the word should contain. </param> /// <param name="filePath">Reference to path of file. If the list will need curation, it will store path to source list. /// If list is there, it will store the path to the curated list.</param> /// <returns>Whether there is a need to run the curation procedure or not.</returns> private bool NeedsCuration(BoggleLists list, int minWordLength, string wordlistContainer, ref string filePath) { bool needsCuration = false; switch (list) { case BoggleLists.TWS: filePath = GetAbsoluteFilePath(string.Format("{0}/{1}.{2}.{3}", filePath, Constants.TWS_FILE_PATH, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString())); if (filePath == string.Empty) { filePath = string.Format("{0}/{1}", wordlistContainer, Constants.TWS_FILE_PATH); needsCuration = true; } break; case BoggleLists.ZINGARELLI: filePath = GetAbsoluteFilePath(string.Format("{0}/{1}.{2}.{3}", filePath, Constants.ZINGARELLI_FILE_PATH, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString())); if (filePath == string.Empty) { filePath = string.Format("{0}/{1}", wordlistContainer, Constants.ZINGARELLI_FILE_PATH); needsCuration = true; } break; default: throw new ArgumentException(string.Format("Support for {0} wordlist has not been coded yet :(", list)); } return needsCuration; } } }