|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn this article, I will describe the implementation of an efficient Aho-Corasick algorithm for pattern matching. In simple words, this algorithm can be used for searching a text for specified keywords. The following code is useful when you have a set of keywords and you want to find all occurrences of a keywords in the text or check if any of the keywords is present in the text. You should use this algorithm especially if you have a large number of keywords that don't change often, because in this case, it is much more efficient than other algorithms that can be simply implemented using the .NET class library. Aho-Corasick algorithmIn this section, I'll try to describe the concept of this algorithm. For more information and for a more exact explanation, please take a look at the links at the end of this article. The algorithm consists of two parts. The first part is the building of the tree from keywords you want to search for, and the second part is searching the text for the keywords using the previously built tree (state machine). Searching for a keyword is very efficient, because it only moves through the states in the state machine. If a character is matching, it follows goto function otherwise it follows fail function. Tree buildingIn the first phase of the tree building, keywords are added to the tree. In my implementation, I use the class During the second phase, the fail and output functions are found. The fail function is used when a character is not matching and the output function returns the found keywords for each reached state. For example, in the text "SHIS", the failure function is used to exit from the "SHE" branch to "HIS" branch after the first two characters (because the third character is not matching). During the second phase, the BFS (breadth first search) algorithm is used for traversing through all the nodes. Functions are calculated in this order, because the fail function of the specified node is calculated using the fail function of the parent node.
Building of the keyword tree (figure 1 - after the first step, figure 2 - tree with the fail function) SearchingAs I already mentioned, searching only means traversing the previously built keyword tree (state machine). To demonstrate how this algorithm works, let's look at the commented method which returns all the matches of the specified keywords: // Searches passed text and returns all occurrences of any keyword
// Returns array containing positions of found keywords
public StringSearchResult[] FindAll(string text)
{
ArrayList ret=new ArrayList(); // List containing results
TreeNode ptr=_root; // Current node (state)
int index=0; // Index in text
// Loop through characters
while(index<text.Length)
{
// Find next state (if no transition exists, fail function is used)
// walks through tree until transition is found or root is reached
TreeNode trans=null;
while(trans==null)
{
trans=ptr.GetTransition(text[index]);
if (ptr==_root) break;
if (trans==null) ptr=ptr.Failure;
}
if (trans!=null) ptr=trans;
// Add results from node to output array and move to next character
foreach(string found in ptr.Results)
ret.Add(new StringSearchResult(index-found.Length+1,found));
index++;
}
// Convert results to array
return (StringSearchResult[])ret.ToArray(typeof(StringSearchResult));
}
Algorithm complexityComplexity of the first part is not so important, because it is executed only once. Complexity of the second part is O(m+z) where m is the length of the text and z is the number of found keywords (in simple words, it is very fast and it's speed doesn't drop quickly for longer texts or many keywords). Performance comparisonTo show how efficient this algorithm is, I created a test application which compares this algorithm with two other simple methods that can be used for this purpose. The first algorithm uses the The interesting thing is that for less than 70 keywords, it is better to use a simple method using
Two charts comparing the speed of the three described algorithms - Aho-Corasick (green), IndexOf (blue), and Regex (yellow) How to use the codeI decided to implement this algorithm when I had to ban some words in a community web page (vulgarisms etc.). This is a typical use case because searching should be really fast, but blocked keywords don't change often (and the creation of the keyword tree can be slower). The search algorithm is implemented in a file StringSearch.cs. I created the interface that represents any search algorithm (so it is easy to replace it with another implementation). This interface is called InitializationThe following example shows how to load keywords from a database and create a // Initialize DB connection
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("SELECT BlockedWord" +
" FROM BlockedWords",conn);
conn.Open();
// Read list of banned words
ArrayList listWords = new ArrayList();
using(SqlDataReader reader =
cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while(reader.Read())
listWords.Add(myReader.GetString(0));
}
string[] arrayWords = (string[])listWords.ToArray(typeof(string));
// Create search algorithm instance
IStringSearchAlgorithm searchAlg = new StringSearch();
searchAlg.Keywords = arrayWords;
You can also use the SearchingSearching the passed text for keywords is even easier. The following sample shows how to write all the matches to the console output: // Find all matching keywords
StringSearchResult[] results=searchAlg.FindAll(textToSearch);
// Write all results
foreach(StringSearchResult r in results)
{
Console.WriteLine("Keyword='{0}', Index={1}", r.Keyword, r.Index);
}
ConclusionThis implementation of the Aho-Corasick search algorithm is very efficient if you want to find a large number of keywords in a text of any length, but if you want to search only for a few keywords, it is better to use a simple method like Links and references
Future work and history
| ||||||||||||||||||||