Dev Ghai commited on 2013-09-23 06:35:57
Showing 12 changed files, with 0 additions and 978 deletions.
... | ... |
@@ -1,166 +0,0 @@ |
1 |
-//#define DEVELOPING |
|
2 |
- |
|
3 |
-using System; |
|
4 |
-using System.Collections.Generic; |
|
5 |
-using System.IO; |
|
6 |
-using System.Diagnostics; |
|
7 |
- |
|
8 |
-namespace Boggle |
|
9 |
-{ |
|
10 |
- public class Boggle |
|
11 |
- { |
|
12 |
- static void PrintWords(HashSet<WordOnBoard> words) |
|
13 |
- { |
|
14 |
- int score = 0; |
|
15 |
- foreach (WordOnBoard word in words) |
|
16 |
- { |
|
17 |
- Console.Write("{0} [{1}], ", word.Word, word.Score); |
|
18 |
- score += word.Score; |
|
19 |
- } |
|
20 |
- Console.WriteLine("\n\nTotal score for this board: {0}", score); |
|
21 |
- } |
|
22 |
- static void Main(string[] args) |
|
23 |
- { |
|
24 |
- Stopwatch st = new Stopwatch(); |
|
25 |
- BoggleList bl = new BoggleList(); |
|
26 |
- Console.WriteLine("Current directory: {0}", Directory.GetCurrentDirectory()); |
|
27 |
-#if !DEVELOPING |
|
28 |
- string input; |
|
29 |
- bool isError = false; |
|
30 |
-#endif |
|
31 |
- int boardSideLength = 3; |
|
32 |
- int minWordLength = 4; |
|
33 |
- |
|
34 |
- char wordListInput = 'Z'; |
|
35 |
- BoggleLists listToUseForLookup; |
|
36 |
- |
|
37 |
-#if !DEVELOPING |
|
38 |
- do |
|
39 |
- { |
|
40 |
- isError = false; |
|
41 |
- Console.WriteLine("Please enter the word list that you wish to use [Z for Zingarelli, T for TWS or standard scrabble list]: "); |
|
42 |
- input = Console.ReadLine(); |
|
43 |
- if (!char.TryParse(input, out wordListInput)) |
|
44 |
- { |
|
45 |
- Console.WriteLine("Invalid Input. Couldn't parse character"); |
|
46 |
- isError = true; |
|
47 |
- } |
|
48 |
- wordListInput = char.ToUpper(wordListInput); |
|
49 |
- if (wordListInput != 'T' && wordListInput != 'Z') |
|
50 |
- { |
|
51 |
- Console.WriteLine("Please enter only T or Z."); |
|
52 |
- isError = true; |
|
53 |
- } |
|
54 |
- } while (isError); |
|
55 |
-#endif |
|
56 |
- switch (wordListInput) |
|
57 |
- { |
|
58 |
- case 'T': |
|
59 |
- listToUseForLookup = BoggleLists.TWS; |
|
60 |
- break; |
|
61 |
- |
|
62 |
- case 'Z': |
|
63 |
- listToUseForLookup = BoggleLists.ZINGARELLI; |
|
64 |
- break; |
|
65 |
- default: |
|
66 |
- throw new Exception("Unexpected!!"); |
|
67 |
- } |
|
68 |
- |
|
69 |
-#if !DEVELOPING |
|
70 |
- do |
|
71 |
- { |
|
72 |
- isError = false; |
|
73 |
- Console.WriteLine("Please enter number of tiles that make up one side of the board: "); |
|
74 |
- input = Console.ReadLine(); |
|
75 |
- if (!int.TryParse(input, out boardSideLength)) |
|
76 |
- { |
|
77 |
- Console.WriteLine("Invalid Input."); |
|
78 |
- isError = true; |
|
79 |
- } |
|
80 |
- } while (isError); |
|
81 |
- |
|
82 |
- do |
|
83 |
- { |
|
84 |
- isError = false; |
|
85 |
- Console.WriteLine("Please enter number of ALPHABETS that should be there in shortest word: "); |
|
86 |
- input = Console.ReadLine(); |
|
87 |
- if (!int.TryParse(input, out minWordLength)) |
|
88 |
- { |
|
89 |
- Console.WriteLine("Invalid Input."); |
|
90 |
- isError = true; |
|
91 |
- } |
|
92 |
- } while (isError); |
|
93 |
-#endif |
|
94 |
- st.Start(); |
|
95 |
- bl.LoadList(listToUseForLookup, boardSideLength, minWordLength, Directory.GetCurrentDirectory()); |
|
96 |
- st.Stop(); |
|
97 |
- Console.WriteLine("Loaded list in {0} ms.", st.ElapsedMilliseconds); |
|
98 |
- |
|
99 |
- string boardString = "qwertyuio"; |
|
100 |
-#if !DEVELOPING |
|
101 |
- do |
|
102 |
- { |
|
103 |
- isError = false; |
|
104 |
- Console.WriteLine("Please enter the characters on board in row first fashion ({0} expected characters): ", boardSideLength*boardSideLength); |
|
105 |
- boardString = Console.ReadLine(); |
|
106 |
- if (boardString.Length != boardSideLength*boardSideLength) |
|
107 |
- { |
|
108 |
- Console.WriteLine("Invalid Input. Q will be translated to QU internally. Please enter only Q if you want to enter the QU tile."); |
|
109 |
- isError = true; |
|
110 |
- } |
|
111 |
- } while (isError); |
|
112 |
-#endif |
|
113 |
- boardString = boardString.ToUpper(); |
|
114 |
- |
|
115 |
- BoggleBoard board = new BoggleBoard(boardSideLength, boardString); |
|
116 |
- board.Print(); |
|
117 |
- BoggleSolver solution = new BoggleSolver(board, bl, minWordLength); |
|
118 |
- st.Start(); |
|
119 |
- //HashSet<WordOnBoard> wordsUnThreaded = solution.GetWordsOnBoard(false); |
|
120 |
- //st.Stop(); |
|
121 |
- //PrintWords(wordsUnThreaded); |
|
122 |
- |
|
123 |
- //Console.WriteLine("Got solution in {0} ms. Number of words (unthreaded): {1}", st.ElapsedMilliseconds, wordsUnThreaded.Count); |
|
124 |
- //Console.WriteLine("Press any key to run in threaded mode."); |
|
125 |
- //Console.ReadKey(); |
|
126 |
- st.Start(); |
|
127 |
- HashSet<WordOnBoard> wordsThreaded = solution.GetWordsOnBoard(true); |
|
128 |
- //wordsThreaded.ExceptWith(wordsUnThreaded); |
|
129 |
- st.Stop(); |
|
130 |
- st.Reset(); |
|
131 |
- PrintWords(wordsThreaded); |
|
132 |
- Console.WriteLine("Got solution in {0} ms. Number of words (threaded): {1}.", st.ElapsedMilliseconds, wordsThreaded.Count); |
|
133 |
- |
|
134 |
- |
|
135 |
- |
|
136 |
- |
|
137 |
- //if (wordsUnThreaded.Count > wordsThreaded.Count) |
|
138 |
- //{ |
|
139 |
- // wordsUnThreaded.ExceptWith(wordsThreaded); |
|
140 |
- // Console.WriteLine("Words in unthreaded collection that are not in threaded collection:"); |
|
141 |
- // PrintWords(wordsUnThreaded); |
|
142 |
- //} |
|
143 |
- //if (wordsThreaded.Count > wordsUnThreaded.Count) |
|
144 |
- //{ |
|
145 |
- // wordsThreaded.ExceptWith(wordsUnThreaded); |
|
146 |
- // Console.WriteLine("Words in threaded collection that are not in unthreaded collection:"); |
|
147 |
- // PrintWords(wordsThreaded); |
|
148 |
- //} |
|
149 |
- |
|
150 |
- //wordsUnThreaded.RemoveWhere(WordsWithLowestScore); |
|
151 |
- //Console.WriteLine("***Removed words with score 1.***"); |
|
152 |
- //PrintWords(wordsUnThreaded); |
|
153 |
- Console.ReadKey(); |
|
154 |
- } |
|
155 |
- |
|
156 |
- private static bool WordsWithLowestScore(WordOnBoard word) |
|
157 |
- { |
|
158 |
- if (word.Score == Constants.MIN_SCORE) |
|
159 |
- { |
|
160 |
- return true; |
|
161 |
- } |
|
162 |
- |
|
163 |
- return false; |
|
164 |
- } |
|
165 |
- } |
|
166 |
-} |
... | ... |
@@ -1,71 +0,0 @@ |
1 |
-<?xml version="1.0" encoding="utf-8"?> |
|
2 |
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|
3 |
- <PropertyGroup> |
|
4 |
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
|
5 |
- <Platform Condition=" '$(Platform)' == '' ">x86</Platform> |
|
6 |
- <ProductVersion>8.0.30703</ProductVersion> |
|
7 |
- <SchemaVersion>2.0</SchemaVersion> |
|
8 |
- <ProjectGuid>{AB752D5B-37CC-4029-8621-FA970CDD5E2A}</ProjectGuid> |
|
9 |
- <OutputType>Exe</OutputType> |
|
10 |
- <AppDesignerFolder>Properties</AppDesignerFolder> |
|
11 |
- <RootNamespace>Boggle</RootNamespace> |
|
12 |
- <AssemblyName>Boggle</AssemblyName> |
|
13 |
- <FileAlignment>512</FileAlignment> |
|
14 |
- </PropertyGroup> |
|
15 |
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> |
|
16 |
- <PlatformTarget>x86</PlatformTarget> |
|
17 |
- <DebugSymbols>true</DebugSymbols> |
|
18 |
- <DebugType>full</DebugType> |
|
19 |
- <Optimize>false</Optimize> |
|
20 |
- <OutputPath>bin\Debug\</OutputPath> |
|
21 |
- <DefineConstants>DEBUG;TRACE</DefineConstants> |
|
22 |
- <ErrorReport>prompt</ErrorReport> |
|
23 |
- <WarningLevel>4</WarningLevel> |
|
24 |
- </PropertyGroup> |
|
25 |
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> |
|
26 |
- <PlatformTarget>x86</PlatformTarget> |
|
27 |
- <DebugType>pdbonly</DebugType> |
|
28 |
- <Optimize>true</Optimize> |
|
29 |
- <OutputPath>bin\Release\</OutputPath> |
|
30 |
- <DefineConstants>TRACE</DefineConstants> |
|
31 |
- <ErrorReport>prompt</ErrorReport> |
|
32 |
- <WarningLevel>4</WarningLevel> |
|
33 |
- </PropertyGroup> |
|
34 |
- <ItemGroup> |
|
35 |
- <Reference Include="System" /> |
|
36 |
- <Reference Include="System.Core" /> |
|
37 |
- <Reference Include="System.Xml.Linq" /> |
|
38 |
- <Reference Include="System.Data.DataSetExtensions" /> |
|
39 |
- <Reference Include="Microsoft.CSharp" /> |
|
40 |
- <Reference Include="System.Data" /> |
|
41 |
- <Reference Include="System.Xml" /> |
|
42 |
- </ItemGroup> |
|
43 |
- <ItemGroup> |
|
44 |
- <Compile Include="Boggle.cs" /> |
|
45 |
- <Compile Include="BoggleBoard.cs" /> |
|
46 |
- <Compile Include="BoggleList.cs" /> |
|
47 |
- <Compile Include="BoggleSolver.cs" /> |
|
48 |
- <Compile Include="Constants.cs" /> |
|
49 |
- <Compile Include="ListWord.cs" /> |
|
50 |
- <Compile Include="Properties\AssemblyInfo.cs" /> |
|
51 |
- <Compile Include="Tile.cs" /> |
|
52 |
- <Compile Include="WordComparer.cs" /> |
|
53 |
- <Compile Include="WordOnBoard.cs" /> |
|
54 |
- </ItemGroup> |
|
55 |
- <ItemGroup> |
|
56 |
- <Content Include="wordlists\TWL06.txt"> |
|
57 |
- <CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|
58 |
- </Content> |
|
59 |
- <Content Include="wordlists\zingarelli2005.txt"> |
|
60 |
- <CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|
61 |
- </Content> |
|
62 |
- </ItemGroup> |
|
63 |
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
|
64 |
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it. |
|
65 |
- Other similar extension points exist, see Microsoft.Common.targets. |
|
66 |
- <Target Name="BeforeBuild"> |
|
67 |
- </Target> |
|
68 |
- <Target Name="AfterBuild"> |
|
69 |
- </Target> |
|
70 |
- --> |
|
71 |
-</Project> |
|
72 | 0 |
\ No newline at end of file |
... | ... |
@@ -1,20 +0,0 @@ |
1 |
- |
|
2 |
-Microsoft Visual Studio Solution File, Format Version 11.00 |
|
3 |
-# Visual Studio 2010 |
|
4 |
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Boggle", "Boggle.csproj", "{AB752D5B-37CC-4029-8621-FA970CDD5E2A}" |
|
5 |
-EndProject |
|
6 |
-Global |
|
7 |
- GlobalSection(SolutionConfigurationPlatforms) = preSolution |
|
8 |
- Debug|x86 = Debug|x86 |
|
9 |
- Release|x86 = Release|x86 |
|
10 |
- EndGlobalSection |
|
11 |
- GlobalSection(ProjectConfigurationPlatforms) = postSolution |
|
12 |
- {AB752D5B-37CC-4029-8621-FA970CDD5E2A}.Debug|x86.ActiveCfg = Debug|x86 |
|
13 |
- {AB752D5B-37CC-4029-8621-FA970CDD5E2A}.Debug|x86.Build.0 = Debug|x86 |
|
14 |
- {AB752D5B-37CC-4029-8621-FA970CDD5E2A}.Release|x86.ActiveCfg = Release|x86 |
|
15 |
- {AB752D5B-37CC-4029-8621-FA970CDD5E2A}.Release|x86.Build.0 = Release|x86 |
|
16 |
- EndGlobalSection |
|
17 |
- GlobalSection(MonoDevelopProperties) = preSolution |
|
18 |
- StartupItem = Boggle.csproj |
|
19 |
- EndGlobalSection |
|
20 |
-EndGlobal |
... | ... |
@@ -1,128 +0,0 @@ |
1 |
-using System; |
|
2 |
-using System.Text; |
|
3 |
- |
|
4 |
-namespace Boggle |
|
5 |
-{ |
|
6 |
- public class BoggleBoard |
|
7 |
- { |
|
8 |
- private readonly Tile[,] _Tiles; |
|
9 |
- /// <summary> |
|
10 |
- /// A 2D array containing all the tles in the board. |
|
11 |
- /// This is a read only propert |
|
12 |
- /// </summary> |
|
13 |
- public Tile[,] Tiles |
|
14 |
- { |
|
15 |
- get |
|
16 |
- { |
|
17 |
- return _Tiles; |
|
18 |
- } |
|
19 |
- } |
|
20 |
- |
|
21 |
- private readonly int _SideLength; |
|
22 |
- /// <summary> |
|
23 |
- /// Number of tiles in one side of the square board. |
|
24 |
- /// This is a read only property. |
|
25 |
- /// </summary> |
|
26 |
- public int SideLength |
|
27 |
- { |
|
28 |
- get |
|
29 |
- { |
|
30 |
- return _SideLength; |
|
31 |
- } |
|
32 |
- } |
|
33 |
- |
|
34 |
- /// <summary> |
|
35 |
- /// BoggleBoard constructor. |
|
36 |
- /// </summary> |
|
37 |
- /// <param name="sideLength">Number of TILES that make up the side of the board.</param> |
|
38 |
- /// <param name="letters">String containing alphabets in row first order.</param> |
|
39 |
- public BoggleBoard(int sideLength, string letters) |
|
40 |
- { |
|
41 |
- if (sideLength < Constants.MIN_SIDE_LENGTH) |
|
42 |
- { |
|
43 |
- throw new ArgumentException(string.Format("Length of side of board has to be greater than or equal to {0}", Constants.MIN_SIDE_LENGTH)); |
|
44 |
- } |
|
45 |
- |
|
46 |
- int numTiles = sideLength * sideLength; |
|
47 |
- |
|
48 |
- if (letters.Length != numTiles) |
|
49 |
- { |
|
50 |
- throw new ArgumentException(string.Format("Board's initialization string contains {0} characters. {1} characters were expected. 'Q' tranlates to 'QU' internally and only 'Q' needs to be mentioned.", letters.Length, numTiles)); |
|
51 |
- } |
|
52 |
- _SideLength = sideLength; |
|
53 |
- _Tiles = new Tile[_SideLength, _SideLength]; |
|
54 |
- |
|
55 |
- //row-first addition = manner in which humans read. |
|
56 |
- string alphabet; |
|
57 |
- for (int i = 0; i < sideLength; i++) |
|
58 |
- { |
|
59 |
- for (int j = 0; j < sideLength; j++) |
|
60 |
- { |
|
61 |
- alphabet = letters.Substring(i * sideLength + j, 1); |
|
62 |
- //the special condition |
|
63 |
- if (alphabet == "Q") |
|
64 |
- alphabet += "U"; |
|
65 |
- _Tiles[j, i] = new Tile(j, i, alphabet); |
|
66 |
- } |
|
67 |
- } |
|
68 |
- } |
|
69 |
- |
|
70 |
- /// <summary> |
|
71 |
- /// Copy constructor. |
|
72 |
- /// </summary> |
|
73 |
- /// <param name="orignalBoard">Board to copy.</param> |
|
74 |
- public BoggleBoard(BoggleBoard orignalBoard) |
|
75 |
- { |
|
76 |
- _SideLength = orignalBoard._SideLength; |
|
77 |
- _Tiles = new Tile[_SideLength, _SideLength]; |
|
78 |
- for (int i = 0; i < _SideLength; i++) |
|
79 |
- { |
|
80 |
- for (int j = 0; j < _SideLength; j++) |
|
81 |
- { |
|
82 |
- _Tiles[i, j] = new Tile(i, j, orignalBoard.Tiles[i, j].Alphabet); |
|
83 |
- } |
|
84 |
- } |
|
85 |
- } |
|
86 |
- |
|
87 |
- /// <summary> |
|
88 |
- /// Indexer to allow more natural access to tiles in the board. |
|
89 |
- /// </summary> |
|
90 |
- /// <param name="x">X co-ordinate of the tile in the board.</param> |
|
91 |
- /// <param name="y">Y co-ordinate of the tile in the board.</param> |
|
92 |
- /// <returns>Tile at x, y co-ordinate.</returns> |
|
93 |
- public Tile this[int x, int y] |
|
94 |
- { |
|
95 |
- get |
|
96 |
- { |
|
97 |
- return _Tiles[x, y]; |
|
98 |
- } |
|
99 |
- } |
|
100 |
- |
|
101 |
- /// <summary> |
|
102 |
- /// Prints out the board on console. |
|
103 |
- /// </summary> |
|
104 |
- public void Print() |
|
105 |
- { |
|
106 |
- int horizontalLength = _SideLength * 4 + 1; |
|
107 |
- StringBuilder line = new StringBuilder(horizontalLength); |
|
108 |
- string horizontalSeparator = string.Empty; |
|
109 |
- for (int i = 0; i < horizontalLength; i++) |
|
110 |
- { |
|
111 |
- horizontalSeparator += "\u2550"; |
|
112 |
- } |
|
113 |
- |
|
114 |
- for (int i = 0; i < _SideLength; i++) |
|
115 |
- { |
|
116 |
- Console.WriteLine(horizontalSeparator); |
|
117 |
- line.Clear(); |
|
118 |
- line.Append("\u2551"); |
|
119 |
- for (int j = 0; j < _SideLength; j++) |
|
120 |
- { |
|
121 |
- line.AppendFormat(" {0} \u2551", _Tiles[j, i].Alphabet); |
|
122 |
- } |
|
123 |
- Console.WriteLine(line); |
|
124 |
- } |
|
125 |
- Console.WriteLine(horizontalSeparator); |
|
126 |
- } |
|
127 |
- } |
|
128 |
-} |
... | ... |
@@ -1,226 +0,0 @@ |
1 |
-using System; |
|
2 |
-using System.Collections.Generic; |
|
3 |
-using System.IO; |
|
4 |
- |
|
5 |
-namespace Boggle |
|
6 |
-{ |
|
7 |
- /// <summary> |
|
8 |
- /// Supported lists. |
|
9 |
- /// </summary> |
|
10 |
- public enum BoggleLists |
|
11 |
- { |
|
12 |
- /// <summary> |
|
13 |
- /// Scrabble dictionary for USA, Canada and Thailand |
|
14 |
- /// </summary> |
|
15 |
- TWS, |
|
16 |
- |
|
17 |
- /// <summary> |
|
18 |
- /// Scrabble dictionary used world wide except in USA, Canada and Thailand. |
|
19 |
- /// </summary> |
|
20 |
- //SOWPODS, |
|
21 |
- |
|
22 |
- /// <summary> |
|
23 |
- /// A huge dictionary found on official Internet Scrabble Club's website (http://www.isc.ro/en/commands/lists.html) |
|
24 |
- /// </summary> |
|
25 |
- ZINGARELLI |
|
26 |
- } |
|
27 |
- |
|
28 |
- public class BoggleList |
|
29 |
- { |
|
30 |
- /// <summary> |
|
31 |
- /// Contains a hybrid tree that has been optimized for recursive search. |
|
32 |
- /// </summary> |
|
33 |
- 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) |
|
34 |
- public Dictionary<string, ListWord> Wordlist |
|
35 |
- { |
|
36 |
- get |
|
37 |
- { |
|
38 |
- return _WordList; |
|
39 |
- } |
|
40 |
- } |
|
41 |
- |
|
42 |
- /// <summary> |
|
43 |
- /// Contains a list of dictionaries that have been loaded, indexed by enum that contains dictionaries we support. |
|
44 |
- /// </summary> |
|
45 |
- private Dictionary<BoggleLists, Dictionary<string, ListWord>> _LoadedDictionaries; |
|
46 |
- |
|
47 |
- /// <summary> |
|
48 |
- /// Determines if we have an absolute path |
|
49 |
- /// </summary> |
|
50 |
- /// <param name="filePath">File that contains the list SORTED list of words. Path can be relative or absolute.</param> |
|
51 |
- /// <returns>The absolute file path that we can use to access it.</returns> |
|
52 |
- private string GetAbsoluteFilePath(string filePath) |
|
53 |
- { |
|
54 |
- //check if the absolute file path exists. |
|
55 |
- if (!File.Exists(filePath)) |
|
56 |
- { |
|
57 |
- //try checking if this is a relative file path and prepend current directory's path. |
|
58 |
- filePath = string.Format("{0}\\{1}", Directory.GetCurrentDirectory(), filePath); |
|
59 |
- if (!File.Exists(filePath)) |
|
60 |
- { |
|
61 |
- filePath = string.Empty; |
|
62 |
- } |
|
63 |
- } |
|
64 |
- |
|
65 |
- return filePath; |
|
66 |
- } |
|
67 |
- |
|
68 |
- /// <summary> |
|
69 |
- /// Curates the list for use by this solver. Basically culls out words that do not meet the criteria. |
|
70 |
- /// Criteria: Min_word_length < word_length < Max_word_length. Max_word_length = sideLength^2 + 1. |
|
71 |
- /// </summary> |
|
72 |
- /// <param name="filePath"> Path to file that contains the SORTED list of words. |
|
73 |
- /// It can be relative to current execution directory or absolute.</param> |
|
74 |
- /// <param name="boardSideLength">Length of the side of board in number of tiles.</param> |
|
75 |
- /// <param name="minWordLength">Number of alphabets that can be there in the shortest word on the board.</param> |
|
76 |
- private void CurateList(string filePath, int boardSideLength, int minWordLength) |
|
77 |
- { |
|
78 |
- //filePath = GetAbsoluteFilePath(filePath); |
|
79 |
- //true tells stream reader to auto detect encoding of the file. |
|
80 |
- StreamReader rawList = new StreamReader(filePath, true); |
|
81 |
- //Create a new file. Do not append anything. |
|
82 |
- string curatedFilePath = string.Format("{0}.{1}.{2}", filePath, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString()); |
|
83 |
- StreamWriter curatedList = new StreamWriter(curatedFilePath, false); |
|
84 |
- |
|
85 |
- string word; |
|
86 |
- //write out the words whose number of characters are between the shortest and largest provided length. |
|
87 |
- while ((word = rawList.ReadLine()) != null) |
|
88 |
- { |
|
89 |
- if (word.Length >= minWordLength && word.Length <= boardSideLength * boardSideLength + 1) |
|
90 |
- { |
|
91 |
- curatedList.WriteLine(word); |
|
92 |
- } |
|
93 |
- } |
|
94 |
- |
|
95 |
- curatedList.Flush(); |
|
96 |
- curatedList.Close(); |
|
97 |
- rawList.Close(); |
|
98 |
- } |
|
99 |
- |
|
100 |
- public BoggleList() |
|
101 |
- { |
|
102 |
- //Initialize the dictionary containing the lists. |
|
103 |
- _LoadedDictionaries = new Dictionary<BoggleLists, Dictionary<string, ListWord>>(Enum.GetNames(typeof(BoggleLists)).Length); |
|
104 |
- } |
|
105 |
- |
|
106 |
- /// <summary> |
|
107 |
- /// Loads the list depending on the selected dictionary. |
|
108 |
- /// </summary> |
|
109 |
- /// <param name="dict">Type of dictionary to load.</param> |
|
110 |
- /// <param name="boardSideLength"></param> |
|
111 |
- /// <param name="minWordLength"></param> |
|
112 |
- /// <returns>A hybird tree containing the words with each level containing one alphabet (or QU)</returns> |
|
113 |
- public Dictionary<string, ListWord> LoadList(BoggleLists dict, int boardSideLength, int minWordLength, string listRootPath) |
|
114 |
- { |
|
115 |
- if (_LoadedDictionaries.ContainsKey(dict)) |
|
116 |
- return _LoadedDictionaries[dict]; |
|
117 |
- |
|
118 |
- string filePath = string.Empty; |
|
119 |
- |
|
120 |
- if (NeedsCuration(dict, minWordLength, listRootPath, ref filePath)) |
|
121 |
- CurateList(filePath, boardSideLength, minWordLength); |
|
122 |
- |
|
123 |
- _WordList = new Dictionary<string, ListWord>(27); |
|
124 |
- |
|
125 |
- //true tells stream reader to auto detect encoding of the file. |
|
126 |
- StreamReader rawList = new StreamReader(filePath, true); |
|
127 |
- string word, substring; |
|
128 |
- ListWord prevListWord; |
|
129 |
- int startIndex; |
|
130 |
- |
|
131 |
- while ((word = rawList.ReadLine()) != null) |
|
132 |
- { |
|
133 |
- //First deal with the head node and then deal with following letters. |
|
134 |
- startIndex = 0; |
|
135 |
- word = word.ToUpper(); |
|
136 |
- substring = word.Substring(0, 1); |
|
137 |
- //if the string starts with Q and there is a 'U' following it, then put QU on tile and increment starting index accordingly. |
|
138 |
- if (substring == "Q" && word[1] == 'U') |
|
139 |
- { |
|
140 |
- substring += "U"; |
|
141 |
- startIndex++; |
|
142 |
- } |
|
143 |
- |
|
144 |
- //If there is no space allocated for this letter at root level, allocate some. |
|
145 |
- if (!_WordList.ContainsKey(substring)) |
|
146 |
- { |
|
147 |
- _WordList.Add(substring, new ListWord()); |
|
148 |
- } |
|
149 |
- prevListWord = _WordList[substring]; |
|
150 |
- |
|
151 |
- //fit the word in the tree structure |
|
152 |
- for (int i = startIndex + 1; i < word.Length; i++) |
|
153 |
- { |
|
154 |
- //We are dealing with one letter at each level |
|
155 |
- substring = word.Substring(i, 1); |
|
156 |
- //But if the letter is Q and there is a U following it, then treat this as one letter. |
|
157 |
- if (substring == "Q" && i + 1 < word.Length && word[i + 1] == 'U') |
|
158 |
- { |
|
159 |
- substring += "U"; |
|
160 |
- //also increment the index as we have consumed the next letter. |
|
161 |
- i++; |
|
162 |
- } |
|
163 |
- //Has this combination of letters appeared before? |
|
164 |
- if (prevListWord.Next == null) |
|
165 |
- { |
|
166 |
- //if not, then make some space to store this combination |
|
167 |
- prevListWord.Next = new Dictionary<string, ListWord>(27); |
|
168 |
- //and store it. |
|
169 |
- prevListWord.Next.Add(substring, new ListWord()); |
|
170 |
- } |
|
171 |
- //if it has, |
|
172 |
- else if (!prevListWord.Next.ContainsKey(substring)) |
|
173 |
- { |
|
174 |
- //then just store it. |
|
175 |
- prevListWord.Next.Add(substring, new ListWord()); |
|
176 |
- } |
|
177 |
- |
|
178 |
- //Update the dictionary to point to current substring's last character/entry in the tree. |
|
179 |
- prevListWord = prevListWord.Next[substring]; |
|
180 |
- } |
|
181 |
- //set the value of the leaf node to be true to point that we have completed the word. |
|
182 |
- prevListWord.IsWord = true; |
|
183 |
- } |
|
184 |
- |
|
185 |
- //Store it in the collection |
|
186 |
- _LoadedDictionaries.Add(dict, _WordList); |
|
187 |
- return _WordList; |
|
188 |
- } |
|
189 |
- |
|
190 |
- /// <summary> |
|
191 |
- /// Checks to see if the proper file exists that contains the appropriate words. |
|
192 |
- /// </summary> |
|
193 |
- /// <param name="list">Type of list to check for.</param> |
|
194 |
- /// <param name="minWordLength">Minimum number of LETTERS the word should contain. </param> |
|
195 |
- /// <param name="filePath">Reference to path of file. If the list will need curation, it will store path to source list. |
|
196 |
- /// If list is there, it will store the path to the curated list.</param> |
|
197 |
- /// <returns>Whether there is a need to run the curation procedure or not.</returns> |
|
198 |
- private bool NeedsCuration(BoggleLists list, int minWordLength, string wordlistContainer, ref string filePath) |
|
199 |
- { |
|
200 |
- bool needsCuration = false; |
|
201 |
- switch (list) |
|
202 |
- { |
|
203 |
- case BoggleLists.TWS: |
|
204 |
- filePath = GetAbsoluteFilePath(string.Format("{0}/{1}.{2}.{3}", filePath, Constants.TWS_FILE_PATH, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString())); |
|
205 |
- if (filePath == string.Empty) |
|
206 |
- { |
|
207 |
- filePath = string.Format("{0}/{1}", wordlistContainer, Constants.TWS_FILE_PATH); |
|
208 |
- needsCuration = true; |
|
209 |
- } |
|
210 |
- break; |
|
211 |
- case BoggleLists.ZINGARELLI: |
|
212 |
- filePath = GetAbsoluteFilePath(string.Format("{0}/{1}.{2}.{3}", filePath, Constants.ZINGARELLI_FILE_PATH, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString())); |
|
213 |
- if (filePath == string.Empty) |
|
214 |
- { |
|
215 |
- filePath = string.Format("{0}/{1}", wordlistContainer, Constants.ZINGARELLI_FILE_PATH); |
|
216 |
- needsCuration = true; |
|
217 |
- } |
|
218 |
- break; |
|
219 |
- default: |
|
220 |
- throw new ArgumentException(string.Format("Support for {0} wordlist has not been coded yet :(", list)); |
|
221 |
- } |
|
222 |
- |
|
223 |
- return needsCuration; |
|
224 |
- } |
|
225 |
- } |
|
226 |
-} |
... | ... |
@@ -1,206 +0,0 @@ |
1 |
-using System; |
|
2 |
-using System.Collections.Generic; |
|
3 |
-using System.Text; |
|
4 |
-using System.Threading; |
|
5 |
- |
|
6 |
-namespace Boggle |
|
7 |
-{ |
|
8 |
- public class BoggleSolver |
|
9 |
- { |
|
10 |
- BoggleList _WordList; |
|
11 |
- WordComparer _wordEqualityComparer; |
|
12 |
- BoggleBoard _Board; |
|
13 |
- int _MinWordLength; |
|
14 |
- HashSet<WordOnBoard> _wordsOnBoard; |
|
15 |
- |
|
16 |
- /// <summary> |
|
17 |
- /// Constructor. |
|
18 |
- /// </summary> |
|
19 |
- /// <param name="board">Board on which we need to run the solution,</param> |
|
20 |
- /// <param name="words">List of valid words.</param> |
|
21 |
- /// <param name="minWordLength">Length of smallest word in number of tiles.</param> |
|
22 |
- public BoggleSolver(BoggleBoard board, BoggleList words, int minWordLength) |
|
23 |
- { |
|
24 |
- _Board = board; |
|
25 |
- _WordList = words; |
|
26 |
- _MinWordLength = minWordLength; |
|
27 |
- _wordEqualityComparer = new WordComparer(); |
|
28 |
- _wordsOnBoard = new HashSet<WordOnBoard>(_wordEqualityComparer); |
|
29 |
- } |
|
30 |
- |
|
31 |
- /// <summary> |
|
32 |
- /// Get the set of words existing on the board. |
|
33 |
- /// </summary> |
|
34 |
- /// <returns>Set of words on the board. We are using a Hashset because it automatically takes care of duplicate words.</returns> |
|
35 |
- public HashSet<WordOnBoard> GetWordsOnBoard(bool useThreads = false) |
|
36 |
- { |
|
37 |
- if (!useThreads) |
|
38 |
- return GetWordsOnBoardNoThreads(); |
|
39 |
- |
|
40 |
- return GetWordsOnBoardThreaded(); |
|
41 |
- } |
|
42 |
- |
|
43 |
- private ManualResetEvent[] doneEvents; |
|
44 |
- |
|
45 |
- /// <summary> |
|
46 |
- /// Entry point for getting all possible words on board in threaded manner. |
|
47 |
- /// </summary> |
|
48 |
- /// <returns>Hashset of all words that can appear on the board.</returns> |
|
49 |
- private HashSet<WordOnBoard> GetWordsOnBoardThreaded() |
|
50 |
- { |
|
51 |
- doneEvents = new ManualResetEvent[_Board.SideLength * _Board.SideLength]; |
|
52 |
- for (int i = 0; i < _Board.SideLength; i++) |
|
53 |
- { |
|
54 |
- for (int j = 0; j < _Board.SideLength; j++) |
|
55 |
- { |
|
56 |
- doneEvents[i * _Board.SideLength + j] = new ManualResetEvent(false); |
|
57 |
- |
|
58 |
- ThreadPool.QueueUserWorkItem(CollectWordsFromPivotThreaded, _Board[i, j]); |
|
59 |
- } |
|
60 |
- } |
|
61 |
- WaitHandle.WaitAll(doneEvents); |
|
62 |
- return _wordsOnBoard; |
|
63 |
- } |
|
64 |
- |
|
65 |
- private void CollectWordsFromPivotThreaded(Object obj) |
|
66 |
- { |
|
67 |
- Tile passedTile = (Tile)obj; |
|
68 |
- //Copy the board |
|
69 |
- BoggleBoard boardForThisThread = new BoggleBoard(this._Board); |
|
70 |
- //Initialize a list that will hold the tiles that will make up the word. Its max size will be square of side. |
|
71 |
- List<Tile> newWordList = new List<Tile>(boardForThisThread.SideLength * boardForThisThread.SideLength); |
|
72 |
- //Figure out the tile in the new board |
|
73 |
- Tile t = boardForThisThread[passedTile.X, passedTile.Y]; |
|
74 |
- //Initialize this list with the tile that we will start finding words from. |
|
75 |
- newWordList.Add(t); |
|
76 |
- //Collect all the possible words from this tile. End result should be in the Hashset. |
|
77 |
- CollectWordsFromPivot(boardForThisThread, _WordList.Wordlist[t.Alphabet].Next, newWordList); |
|
78 |
- doneEvents[t.X * _Board.SideLength + t.Y].Set(); |
|
79 |
- } |
|
80 |
- |
|
81 |
- private HashSet<WordOnBoard> GetWordsOnBoardNoThreads() |
|
82 |
- { |
|
83 |
- for (int i = 0; i < _Board.SideLength; i++) |
|
84 |
- { |
|
85 |
- for (int j = 0; j < _Board.SideLength; j++) |
|
86 |
- { |
|
87 |
- //Collect all the tiles that make up the word. |
|
88 |
- List<Tile> newWordList = new List<Tile>(_Board.SideLength * _Board.SideLength); |
|
89 |
- //Initialize this list with the tile that we will start finding words from. |
|
90 |
- newWordList.Add(_Board[i, j]); |
|
91 |
- //Collect all the possible words from this tile. End result should be in the Hashset. |
|
92 |
- CollectWordsFromPivot(this._Board, _WordList.Wordlist[_Board[i, j].Alphabet].Next, newWordList); |
|
93 |
- //Set everything visited to false so that next traversal has the proper state. |
|
94 |
- ResetBoard(); |
|
95 |
- } |
|
96 |
- } |
|
97 |
- |
|
98 |
- return _wordsOnBoard; |
|
99 |
- } |
|
100 |
- |
|
101 |
- /// <summary> |
|
102 |
- /// Sets IsVisited of every tile in the board to false. |
|
103 |
- /// </summary> |
|
104 |
- private void ResetBoard() |
|
105 |
- { |
|
106 |
- for (int i = 0; i < _Board.SideLength; i++) |
|
107 |
- { |
|
108 |
- for (int j = 0; j < _Board.SideLength; j++) |
|
109 |
- { |
|
110 |
- _Board[i, j].IsVisited = false; |
|
111 |
- } |
|
112 |
- } |
|
113 |
- |
|
114 |
- } |
|
115 |
- |
|
116 |
- /// <summary> |
|
117 |
- /// Extracts the word from tiles and stores it in a synchronized fashion. |
|
118 |
- /// </summary> |
|
119 |
- /// <param name="wordTiles">Tiles making up the word.</param> |
|
120 |
- private void StoreWord(List<Tile> wordTiles) |
|
121 |
- { |
|
122 |
- StringBuilder builder = new StringBuilder(wordTiles[0].Alphabet); |
|
123 |
- for (int i = 1; i < wordTiles.Count; i++) |
|
124 |
- { |
|
125 |
- builder.Append(wordTiles[i].Alphabet); |
|
126 |
- } |
|
127 |
- |
|
128 |
- string word = builder.ToString(); |
|
129 |
- int score = Constants.MIN_SCORE; |
|
130 |
- if (word.Length <= _MinWordLength + 1) |
|
131 |
- score = Constants.MIN_SCORE; |
|
132 |
- else if (word.Length == _MinWordLength + 2) |
|
133 |
- score = Constants.MIN_SCORE + 1; |
|
134 |
- else if (word.Length == _MinWordLength + 3) |
|
135 |
- score = Constants.MIN_SCORE + 2; |
|
136 |
- else if (word.Length == _MinWordLength + 4) |
|
137 |
- score = Constants.MIN_SCORE + 4; |
|
138 |
- else |
|
139 |
- score = Constants.MIN_SCORE + 10; |
|
140 |
- |
|
141 |
- lock (_wordsOnBoard) |
|
142 |
- { |
|
143 |
- _wordsOnBoard.Add(new WordOnBoard(word, score)); |
|
144 |
- } |
|
145 |
- } |
|
146 |
- |
|
147 |
- /// <summary> |
|
148 |
- /// Recursive function to get all the possible words from a tile. |
|
149 |
- /// </summary> |
|
150 |
- /// <param name="t">Tile that needs to be analyzed for words.</param> |
|
151 |
- /// <param name="validNextSubstrings">A dictionary containing all letters that can come after the one contained in the tile.</param> |
|
152 |
- /// <param name="currentSubstring">List of tiles that make up all or part of the word.</param> |
|
153 |
- private void CollectWordsFromPivot(BoggleBoard board, Dictionary<string, ListWord> validNextSubstrings, List<Tile> currentSubstring) |
|
154 |
- { |
|
155 |
- Tile t = currentSubstring[currentSubstring.Count - 1]; |
|
156 |
- t.IsVisited = true; |
|
157 |
- |
|
158 |
- string validWord = string.Empty; |
|
159 |
- |
|
160 |
- int adjPosX = -1, adjPosY = -1; |
|
161 |
- for (int x = -1; x <= 1; x++) |
|
162 |
- { |
|
163 |
- adjPosX = t.X + x; |
|
164 |
- if (adjPosX < 0 || adjPosX >= board.SideLength) //we don't want to hit anything outside the boundries of the board. |
|
165 |
- continue; |
|
166 |
- |
|
167 |
- for (int y = -1; y <= 1; y++) |
|
168 |
- { |
|
169 |
- adjPosY = t.Y + y; |
|
170 |
- if (adjPosY < 0 || adjPosY >= board.SideLength || (adjPosX == t.X && adjPosY == t.Y)) //in third condition, it will point to itself and we dont want that |
|
171 |
- continue; |
|
172 |
- |
|
173 |
- Tile adjacentTile = board[adjPosX, adjPosY]; |
|
174 |
- //no need to parse the tile if it has been already visited or there is no word possible with current string |
|
175 |
- if (adjacentTile.IsVisited || !validNextSubstrings.ContainsKey(adjacentTile.Alphabet)) |
|
176 |
- continue; |
|
177 |
- |
|
178 |
- //either we now have a word or have a part of the word |
|
179 |
- currentSubstring.Add(adjacentTile); |
|
180 |
- |
|
181 |
- //if we have the complete word that has minimum word length we require, add it to the hashet of words |
|
182 |
- if (validNextSubstrings[adjacentTile.Alphabet].IsWord && currentSubstring.Count >= _MinWordLength) |
|
183 |
- StoreWord(currentSubstring); |
|
184 |
- |
|
185 |
- //now we want to see if there is any good reason to proceed with passing control to the adjacent tile. |
|
186 |
- Dictionary<string, ListWord> validNextSubstringsAdjTile = validNextSubstrings[adjacentTile.Alphabet].Next; |
|
187 |
- |
|
188 |
- //there will no more words to make after this... no need to recurse. |
|
189 |
- if (validNextSubstringsAdjTile == null) |
|
190 |
- { |
|
191 |
- //remove the alphabet just added from the end of the string as it will not be hitting the appropriate statement later. |
|
192 |
- currentSubstring.RemoveAt(currentSubstring.Count - 1); |
|
193 |
- continue; |
|
194 |
- } |
|
195 |
- |
|
196 |
- //collect words from the new pivot |
|
197 |
- CollectWordsFromPivot(board, validNextSubstringsAdjTile, currentSubstring); |
|
198 |
- |
|
199 |
- //once used, remove the alphabet from the string and mark the tile as not visited. This is because this tile maybe involved in paths from other tiles. |
|
200 |
- currentSubstring.RemoveAt(currentSubstring.Count - 1); |
|
201 |
- adjacentTile.IsVisited = false; |
|
202 |
- } |
|
203 |
- } |
|
204 |
- } |
|
205 |
- } |
|
206 |
-} |
... | ... |
@@ -1,13 +0,0 @@ |
1 |
-namespace Boggle |
|
2 |
-{ |
|
3 |
- static class Constants |
|
4 |
- { |
|
5 |
- public const string CURATED_FILE_SUFFIX = "curated"; |
|
6 |
- public const int MIN_SIDE_LENGTH = 3; |
|
7 |
- public const int MIN_WORD_LENGTH = 3; |
|
8 |
- public const int MIN_SCORE = 1; |
|
9 |
- public const string TWS_FILE_PATH = @"wordlists/TWL06.txt"; |
|
10 |
- public const string ZINGARELLI_FILE_PATH = @"wordlists/zingarelli2005.txt"; |
|
11 |
- |
|
12 |
- } |
|
13 |
-} |
... | ... |
@@ -1,29 +0,0 @@ |
1 |
-using System.Collections.Generic; |
|
2 |
- |
|
3 |
-namespace Boggle |
|
4 |
-{ |
|
5 |
- public class ListWord |
|
6 |
- { |
|
7 |
- public bool IsWord; |
|
8 |
- public Dictionary<string, ListWord> Next; |
|
9 |
- |
|
10 |
- public ListWord() |
|
11 |
- { |
|
12 |
- this.IsWord = false; |
|
13 |
- this.Next = null; |
|
14 |
- } |
|
15 |
- |
|
16 |
- public ListWord(bool isWord) |
|
17 |
- { |
|
18 |
- this.IsWord = isWord; |
|
19 |
- this.Next = null; |
|
20 |
- } |
|
21 |
- |
|
22 |
- public ListWord(bool isWord, Dictionary<string, ListWord> next) |
|
23 |
- { |
|
24 |
- this.IsWord = isWord; |
|
25 |
- this.Next = next; |
|
26 |
- } |
|
27 |
- |
|
28 |
- } |
|
29 |
-} |
... | ... |
@@ -1,70 +0,0 @@ |
1 |
-namespace Boggle |
|
2 |
-{ |
|
3 |
- public class Tile |
|
4 |
- { |
|
5 |
- //declaring readonly as these values will never be modified once set and compiler has ways to optimize for this. |
|
6 |
- private readonly int _X; |
|
7 |
- /// <summary> |
|
8 |
- /// X co-ordinate of the tile in the board array. |
|
9 |
- /// This is a read only property. |
|
10 |
- /// </summary> |
|
11 |
- public int X |
|
12 |
- { |
|
13 |
- get |
|
14 |
- { |
|
15 |
- return _X; |
|
16 |
- } |
|
17 |
- } |
|
18 |
- |
|
19 |
- /// <summary> |
|
20 |
- /// Y co-ordinate of the tile in the board array. |
|
21 |
- /// This is a read only property. |
|
22 |
- /// </summar |
|
23 |
- private readonly int _Y; |
|
24 |
- public int Y |
|
25 |
- { |
|
26 |
- get |
|
27 |
- { |
|
28 |
- return _Y; |
|
29 |
- } |
|
30 |
- } |
|
31 |
- |
|
32 |
- /// <summary> |
|
33 |
- /// The alphabet contained in the tile. It can be any valid letter or 'QU'. |
|
34 |
- /// This is a read only property. |
|
35 |
- /// </summary> |
|
36 |
- private readonly string _Alphabet; |
|
37 |
- public string Alphabet |
|
38 |
- { |
|
39 |
- get |
|
40 |
- { |
|
41 |
- return _Alphabet; |
|
42 |
- } |
|
43 |
- } |
|
44 |
- |
|
45 |
- /// <summary> |
|
46 |
- /// Tells whether this tile has been visited or not during recursion. |
|
47 |
- /// </summary> |
|
48 |
- public bool IsVisited { get; set; } |
|
49 |
- |
|
50 |
- public Tile(int x, int y, string alphabet) |
|
51 |
- { |
|
52 |
- _X = x; |
|
53 |
- _Y = y; |
|
54 |
- _Alphabet = alphabet; |
|
55 |
- IsVisited = false; |
|
56 |
- } |
|
57 |
- |
|
58 |
- /// <summary> |
|
59 |
- /// Copy constructor to use when using threads. |
|
60 |
- /// </summary> |
|
61 |
- /// <param name="t"></param> |
|
62 |
- public Tile(Tile t) |
|
63 |
- { |
|
64 |
- _X = t._X; |
|
65 |
- _Y = t._Y; |
|
66 |
- _Alphabet = t._Alphabet; |
|
67 |
- IsVisited = t.IsVisited; |
|
68 |
- } |
|
69 |
- } |
|
70 |
-} |
... | ... |
@@ -1,28 +0,0 @@ |
1 |
-using System.Collections.Generic; |
|
2 |
- |
|
3 |
-namespace Boggle |
|
4 |
-{ |
|
5 |
- /// <summary> |
|
6 |
- /// Equality comparer used for HashSet. No longer used as I switched to Dictionaries. |
|
7 |
- /// </summary> |
|
8 |
- public class WordComparer : EqualityComparer<WordOnBoard> |
|
9 |
- { |
|
10 |
- public override bool Equals(WordOnBoard x, WordOnBoard y) |
|
11 |
- { |
|
12 |
- return string.Compare(x.Word, y.Word, true) == 0; |
|
13 |
- } |
|
14 |
- |
|
15 |
- public override int GetHashCode(WordOnBoard obj) |
|
16 |
- { |
|
17 |
- int hash = 0; |
|
18 |
- for (int i = 0; i < obj.Word.Length; i++) |
|
19 |
- { |
|
20 |
- hash += (int)obj.Word[i] * 10 ^ (obj.Word.Length - 1 - i); |
|
21 |
- |
|
22 |
- //cat would result in 99 * 10^2 + 97 * 10^1 + 116 * 10^0 = 10986 |
|
23 |
- } |
|
24 |
- |
|
25 |
- return hash; |
|
26 |
- } |
|
27 |
- } |
|
28 |
-} |