FuzzyTermEnum.cs
上传用户:zhangkuixh
上传日期:2013-09-30
资源大小:5473k
文件大小:12k
源码类别:

搜索引擎

开发平台:

C#

  1. /*
  2.  * Copyright 2004 The Apache Software Foundation
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  * 
  8.  * http://www.apache.org/licenses/LICENSE-2.0
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. using System;
  17. using IndexReader = Lucene.Net.Index.IndexReader;
  18. using Term = Lucene.Net.Index.Term;
  19. namespace Lucene.Net.Search
  20. {
  21. /// <summary>Subclass of FilteredTermEnum for enumerating all terms that are similiar
  22. /// to the specified filter term.
  23. /// 
  24. /// <p>Term enumerations are always ordered by Term.compareTo().  Each term in
  25. /// the enumeration is greater than all that precede it.
  26. /// </summary>
  27. public sealed class FuzzyTermEnum : FilteredTermEnum
  28. {
  29. /* This should be somewhere around the average long word.
  30. * If it is longer, we waste time and space. If it is shorter, we waste a
  31. * little bit of time growing the array as we encounter longer words.
  32. */
  33. private const int TYPICAL_LONGEST_WORD_IN_INDEX = 19;
  34. /* Allows us save time required to create a new array
  35. * everytime similarity is called.
  36. */
  37. private int[][] d;
  38. private float similarity;
  39. private bool endEnum = false;
  40. private Term searchTerm = null;
  41. private System.String field;
  42. private System.String text;
  43. private System.String prefix;
  44. private float minimumSimilarity;
  45. private float scale_factor;
  46. private int[] maxDistances = new int[TYPICAL_LONGEST_WORD_IN_INDEX];
  47. /// <summary> Creates a FuzzyTermEnum with an empty prefix and a minSimilarity of 0.5f.
  48. /// <p>
  49. /// After calling the constructor the enumeration is already pointing to the first 
  50. /// valid term if such a term exists. 
  51. /// 
  52. /// </summary>
  53. /// <param name="reader">
  54. /// </param>
  55. /// <param name="term">
  56. /// </param>
  57. /// <throws>  IOException </throws>
  58. /// <seealso cref="FuzzyTermEnum(IndexReader, Term, float, int)">
  59. /// </seealso>
  60. public FuzzyTermEnum(IndexReader reader, Term term) : this(reader, term, FuzzyQuery.defaultMinSimilarity, FuzzyQuery.defaultPrefixLength)
  61. {
  62. }
  63. /// <summary> Creates a FuzzyTermEnum with an empty prefix.
  64. /// <p>
  65. /// After calling the constructor the enumeration is already pointing to the first 
  66. /// valid term if such a term exists. 
  67. /// 
  68. /// </summary>
  69. /// <param name="reader">
  70. /// </param>
  71. /// <param name="term">
  72. /// </param>
  73. /// <param name="minSimilarity">
  74. /// </param>
  75. /// <throws>  IOException </throws>
  76. /// <seealso cref="FuzzyTermEnum(IndexReader, Term, float, int)">
  77. /// </seealso>
  78. public FuzzyTermEnum(IndexReader reader, Term term, float minSimilarity) : this(reader, term, minSimilarity, FuzzyQuery.defaultPrefixLength)
  79. {
  80. }
  81. /// <summary> Constructor for enumeration of all terms from specified <code>reader</code> which share a prefix of
  82. /// length <code>prefixLength</code> with <code>term</code> and which have a fuzzy similarity &gt;
  83. /// <code>minSimilarity</code>.
  84. /// <p>
  85. /// After calling the constructor the enumeration is already pointing to the first 
  86. /// valid term if such a term exists. 
  87. /// 
  88. /// </summary>
  89. /// <param name="reader">Delivers terms.
  90. /// </param>
  91. /// <param name="term">Pattern term.
  92. /// </param>
  93. /// <param name="minSimilarity">Minimum required similarity for terms from the reader. Default value is 0.5f.
  94. /// </param>
  95. /// <param name="prefixLength">Length of required common prefix. Default value is 0.
  96. /// </param>
  97. /// <throws>  IOException </throws>
  98. public FuzzyTermEnum(IndexReader reader, Term term, float minSimilarity, int prefixLength) : base()
  99. {
  100. if (minSimilarity >= 1.0f)
  101. throw new System.ArgumentException("minimumSimilarity cannot be greater than or equal to 1");
  102. else if (minSimilarity < 0.0f)
  103. throw new System.ArgumentException("minimumSimilarity cannot be less than 0");
  104. if (prefixLength < 0)
  105. throw new System.ArgumentException("prefixLength cannot be less than 0");
  106. this.minimumSimilarity = minSimilarity;
  107. this.scale_factor = 1.0f / (1.0f - minimumSimilarity);
  108. this.searchTerm = term;
  109. this.field = searchTerm.Field();
  110. //The prefix could be longer than the word.
  111. //It's kind of silly though.  It means we must match the entire word.
  112. int fullSearchTermLength = searchTerm.Text().Length;
  113. int realPrefixLength = prefixLength > fullSearchTermLength?fullSearchTermLength:prefixLength;
  114. this.text = searchTerm.Text().Substring(realPrefixLength);
  115. this.prefix = searchTerm.Text().Substring(0, (realPrefixLength) - (0));
  116. InitializeMaxDistances();
  117. this.d = InitDistanceArray();
  118. SetEnum(reader.Terms(new Term(searchTerm.Field(), prefix)));
  119. }
  120. /// <summary> The termCompare method in FuzzyTermEnum uses Levenshtein distance to 
  121. /// calculate the distance between the given term and the comparing term. 
  122. /// </summary>
  123. protected internal override bool TermCompare(Term term)
  124. {
  125. if (field == term.Field() && term.Text().StartsWith(prefix))
  126. {
  127. System.String target = term.Text().Substring(prefix.Length);
  128. this.similarity = Similarity(target);
  129. return (similarity > minimumSimilarity);
  130. }
  131. endEnum = true;
  132. return false;
  133. }
  134. public override float Difference()
  135. {
  136. return (float) ((similarity - minimumSimilarity) * scale_factor);
  137. }
  138. public override bool EndEnum()
  139. {
  140. return endEnum;
  141. }
  142. /// <summary>***************************
  143. /// Compute Levenshtein distance
  144. /// ****************************
  145. /// </summary>
  146. /// <summary> Finds and returns the smallest of three integers </summary>
  147. private static int min(int a, int b, int c)
  148. {
  149. int t = (a < b) ? a : b;
  150. return (t < c) ? t : c;
  151. }
  152. private int[][] InitDistanceArray()
  153. {
  154. int[][] tmpArray = new int[this.text.Length + 1][];
  155. for (int i = 0; i < this.text.Length + 1; i++)
  156. {
  157. tmpArray[i] = new int[TYPICAL_LONGEST_WORD_IN_INDEX];
  158. }
  159. return tmpArray;
  160. }
  161. /// <summary> <p>Similarity returns a number that is 1.0f or less (including negative numbers)
  162. /// based on how similar the Term is compared to a target term.  It returns
  163. /// exactly 0.0f when
  164. /// <pre>
  165. /// editDistance &lt; maximumEditDistance</pre>
  166. /// Otherwise it returns:
  167. /// <pre>
  168. /// 1 - (editDistance / length)</pre>
  169. /// where length is the length of the shortest term (text or target) including a
  170. /// prefix that are identical and editDistance is the Levenshtein distance for
  171. /// the two words.</p>
  172. /// 
  173. /// <p>Embedded within this algorithm is a fail-fast Levenshtein distance
  174. /// algorithm.  The fail-fast algorithm differs from the standard Levenshtein
  175. /// distance algorithm in that it is aborted if it is discovered that the
  176. /// mimimum distance between the words is greater than some threshold.
  177. /// 
  178. /// <p>To calculate the maximum distance threshold we use the following formula:
  179. /// <pre>
  180. /// (1 - minimumSimilarity) * length</pre>
  181. /// where length is the shortest term including any prefix that is not part of the
  182. /// similarity comparision.  This formula was derived by solving for what maximum value
  183. /// of distance returns false for the following statements:
  184. /// <pre>
  185. /// similarity = 1 - ((float)distance / (float) (prefixLength + Math.min(textlen, targetlen)));
  186. /// return (similarity > minimumSimilarity);</pre>
  187. /// where distance is the Levenshtein distance for the two words.
  188. /// </p>
  189. /// <p>Levenshtein distance (also known as edit distance) is a measure of similiarity
  190. /// between two strings where the distance is measured as the number of character
  191. /// deletions, insertions or substitutions required to transform one string to
  192. /// the other string.
  193. /// </summary>
  194. /// <param name="target">the target word or phrase
  195. /// </param>
  196. /// <returns> the similarity,  0.0 or less indicates that it matches less than the required
  197. /// threshold and 1.0 indicates that the text and target are identical
  198. /// </returns>
  199. private float Similarity(System.String target)
  200. {
  201. lock (this)
  202. {
  203. int m = target.Length;
  204. int n = text.Length;
  205. if (n == 0)
  206. {
  207. //we don't have anything to compare.  That means if we just add
  208. //the letters for m we get the new word
  209. return prefix.Length == 0 ? 0.0f : 1.0f - ((float) m / prefix.Length);
  210. }
  211. if (m == 0)
  212. {
  213. return prefix.Length == 0 ? 0.0f : 1.0f - ((float) n / prefix.Length);
  214. }
  215. int maxDistance = GetMaxDistance(m);
  216. if (maxDistance < System.Math.Abs(m - n))
  217. {
  218. //just adding the characters of m to n or vice-versa results in
  219. //too many edits
  220. //for example "pre" length is 3 and "prefixes" length is 8.  We can see that
  221. //given this optimal circumstance, the edit distance cannot be less than 5.
  222. //which is 8-3 or more precisesly Math.abs(3-8).
  223. //if our maximum edit distance is 4, then we can discard this word
  224. //without looking at it.
  225. return 0.0f;
  226. }
  227. //let's make sure we have enough room in our array to do the distance calculations.
  228. if (d[0].Length <= m)
  229. {
  230. GrowDistanceArray(m);
  231. }
  232. // init matrix d
  233. for (int i = 0; i <= n; i++)
  234. d[i][0] = i;
  235. for (int j = 0; j <= m; j++)
  236. d[0][j] = j;
  237. // start computing edit distance
  238. for (int i = 1; i <= n; i++)
  239. {
  240. int bestPossibleEditDistance = m;
  241. char s_i = text[i - 1];
  242. for (int j = 1; j <= m; j++)
  243. {
  244. if (s_i != target[j - 1])
  245. {
  246. d[i][j] = min(d[i - 1][j], d[i][j - 1], d[i - 1][j - 1]) + 1;
  247. }
  248. else
  249. {
  250. d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1]);
  251. }
  252. bestPossibleEditDistance = System.Math.Min(bestPossibleEditDistance, d[i][j]);
  253. }
  254. //After calculating row i, the best possible edit distance
  255. //can be found by found by finding the smallest value in a given column.
  256. //If the bestPossibleEditDistance is greater than the max distance, abort.
  257. if (i > maxDistance && bestPossibleEditDistance > maxDistance)
  258. {
  259. //equal is okay, but not greater
  260. //the closest the target can be to the text is just too far away.
  261. //this target is leaving the party early.
  262. return 0.0f;
  263. }
  264. }
  265. // this will return less than 0.0 when the edit distance is
  266. // greater than the number of characters in the shorter word.
  267. // but this was the formula that was previously used in FuzzyTermEnum,
  268. // so it has not been changed (even though minimumSimilarity must be
  269. // greater than 0.0)
  270. return 1.0f - ((float) d[n][m] / (float) (prefix.Length + System.Math.Min(n, m)));
  271. }
  272. }
  273. /// <summary> Grow the second dimension of the array, so that we can calculate the
  274. /// Levenshtein difference.
  275. /// </summary>
  276. private void  GrowDistanceArray(int m)
  277. {
  278. for (int i = 0; i < d.Length; i++)
  279. {
  280. d[i] = new int[m + 1];
  281. }
  282. }
  283. /// <summary> The max Distance is the maximum Levenshtein distance for the text
  284. /// compared to some other value that results in score that is
  285. /// better than the minimum similarity.
  286. /// </summary>
  287. /// <param name="m">the length of the "other value"
  288. /// </param>
  289. /// <returns> the maximum levenshtein distance that we care about
  290. /// </returns>
  291. private int GetMaxDistance(int m)
  292. {
  293. return (m < maxDistances.Length)?maxDistances[m]:CalculateMaxDistance(m);
  294. }
  295. private void  InitializeMaxDistances()
  296. {
  297. for (int i = 0; i < maxDistances.Length; i++)
  298. {
  299. maxDistances[i] = CalculateMaxDistance(i);
  300. }
  301. }
  302. private int CalculateMaxDistance(int m)
  303. {
  304. return (int) ((1 - minimumSimilarity) * (System.Math.Min(text.Length, m) + prefix.Length));
  305. }
  306. public override void  Close()
  307. {
  308. base.Close(); //call super.close() and let the garbage collector do its work.
  309. }
  310. }
  311. }