# N-gram and Fast Pattern Extraction Algorithm

By , 31 Oct 2007

Prize winner in Competition "Best C++/MFC article of Aug 2007"

## Introduction

In this article, I introduce a very fast algorithm to extract text patterns from large size text and give statistical information about patterns' frequency and length. Actually, the idea of this algorithm came to me when one of my friends asked me to give him an idea for extracting patterns from text. I told him immediately that he could use the LZW compression algorithm, take the final dictionary and drop the compressed buffer, and then he could have a dictionary containing all text patterns with each pattern frequency. I don't know if he understood me or not, but I decided to do it later. If pattern word count is fixed (N), then it is a generation for N-gram of the input sequence.

## Background

### N-gram

An n-gram is a sub-sequence of n items from a given sequence. n-grams are used in various areas of statistical natural language processing and genetic sequence analysis. The items in question can be characters, words or base pairs according to the application. For example, the sequence of characters "Hatem mostafa helmy" has a 3-gram of ("Hat", "ate", "tem", "em ", "m m", ...), and has a 2-gram of ("Ha", "at", "te", "em", "m ", " m", ...). This n-gram output can be used for a variety of R&D subjects, such as Statistical machine translation and Spell checking.

### Pattern Extraction

Pattern extraction is the process of parsing a sequence of items to find or extract a certain pattern of items. Pattern length can be fixed, as in the n-gram model, or it can be variable. Variable length patterns can be directives to certain rules, like regular expressions. They can also be random and depend on the context and pattern repetition in the patterns dictionary.

## Algorithm Idea for Variable Length Pattern Extraction

The algorithm introduced here is derived from the LZW compression algorithm, which includes a magic idea about generating dictionary items at compression time while parsing the input sequence. If you have no idea about LZW, you can check it out at my article, Fast LZW compression. And of course, the algorithm inherits the speed of my implementation to LZW, plus extra speed for two reasons:

1. The parsing item is a word, not a letter
2. There's no destination buffer, as there is no need for a compressed buffer

The algorithm uses a binary tree to keep extracted patterns that give the algorithm excellent speed at run-time to find and fetch new items to the dictionary. Let us discuss the algorithm pseudo code. We have some figures to clarify the idea with an algorithm flow chart and a simple example.

Example: the input words sequence `w0w1w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6 `

Assume `n` equals 2; then the initial pattern will be `w0w1`. After applying the algorithm steps, the resultant dictionary would be as in the fourth column:

 Input Sequence Pattern Step Dictionary Frequency `w0w1w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1` Words available? `w0w1w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1` Add to dictionary `w0w1` 3 `w0w1w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1` Pattern exists? `w1w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w1w2` Take new pattern `w1w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w1w2` Words available? `w1w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w1w2` Add to dictionary `w1w2` 1 `w1w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w1w2` Pattern exists? `w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w2w3` Take new pattern `w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w2w3` Words available? `w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w2w3` Add to dictionary `w2w3` 2 `w2w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w2w3` Pattern exists? `w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w3w4` Take new pattern `w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w3w4` Words available? `w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w3w4` Add to dictionary `w3w4` 2 `w3w4w0w1w5w6w7w2w3w4w0w1w5w6` `w3w4` Pattern exists? `w4w0w1w5w6w7w2w3w4w0w1w5w6` `w4w0` Take new pattern `w4w0w1w5w6w7w2w3w4w0w1w5w6` `w4w0` Words available? `w4w0w1w5w6w7w2w3w4w0w1w5w6` `w4w0` Add to dictionary `w4w0` 2 `w4w0w1w5w6w7w2w3w4w0w1w5w6` `w4w0` Pattern exists? `w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1` Take new pattern `w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1` Words available? `w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1` Add to dictionary `w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1` Pattern exists? `w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1w5` Add word to pattern `w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1w5` Words available? `w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1w5` Add to dictionary `w0w1w5` 2 `w0w1w5w6w7w2w3w4w0w1w5w6` `w0w1w5` Pattern exists? `w1w5w6w7w2w3w4w0w1w5w6` `w1w5` Take new pattern `w1w5w6w7w2w3w4w0w1w5w6` `w1w5` Words available? `w1w5w6w7w2w3w4w0w1w5w6` `w1w5` Add to dictionary `w1w5` 2 `w1w5w6w7w2w3w4w0w1w5w6` `w1w5` Pattern exists? `w5w6w7w2w3w4w0w1w5w6` `w5w6` Take new pattern `w5w6w7w2w3w4w0w1w5w6` `w5w6` Words available? `w5w6w7w2w3w4w0w1w5w6` `w5w6` Add to dictionary `w5w6` 1 `w5w6w7w2w3w4w0w1w5w6` `w5w6` Pattern exists? `w6w7w2w3w4w0w1w5w6` `w6w7` Take new pattern `w6w7w2w3w4w0w1w5w6` `w6w7` Words available? `w6w7w2w3w4w0w1w5w6` `w6w7` Add to dictionary `w6w7` 1 `w6w7w2w3w4w0w1w5w6` `w6w7` Pattern exists? `w7w2w3w4w0w1w5w6` `w7w2` Take new pattern `w7w2w3w4w0w1w5w6` `w7w2` Words available? `w7w2w3w4w0w1w5w6` `w7w2` Add to dictionary `w7w2` 1 `w7w2w3w4w0w1w5w6` `w7w2` Pattern exists? `w2w3w4w0w1w5w6` `w2w3` Take new pattern `w2w3w4w0w1w5w6` `w2w3` Words available? `w2w3w4w0w1w5w6` `w2w3` Add to dictionary `w2w3w4w0w1w5w6` `w2w3` Pattern exists? `w2w3w4w0w1w5w6` `w2w3w4` Add word to pattern `w2w3w4w0w1w5w6` `w2w3w4` Words available? `w2w3w4w0w1w5w6` `w2w3w4` Add to dictionary `w2w3w4` 1 `w2w3w4w0w1w5w6` `w2w3w4` Pattern exists? `w3w4w0w1w5w6` `w3w4` Take new pattern `w3w4w0w1w5w6` `w3w4` Words available? `w3w4w0w1w5w6` `w3w4` Add to dictionary `w3w4w0w1w5w6` `w3w4` Pattern exists? `w3w4w0w1w5w6` `w3w4w0` Add word to pattern `w3w4w0w1w5w6` `w3w4w0` Words available? `w3w4w0w1w5w6` `w3w4w0` Add to dictionary `w3w4w0` 1 `w3w4w0w1w5w6` `w3w4w0` Pattern exists? `w4w0w1w5w6` `w4w0` Take new pattern `w4w0w1w5w6` `w4w0` Words available? `w4w0w1w5w6` `w4w0` Add to dictionary `w4w0w1w5w6` `w4w0` Pattern exists? `w4w0w1w5w6` `w4w0w1` Add word to pattern `w4w0w1w5w6` `w4w0w1` Words available? `w4w0w1w5w6` `w4w0w1` Add to dictionary `w4w0w1` 1 `w4w0w1w5w6` `w4w0w1` Pattern exists? `w0w1w5w6` `w0w1` Take new pattern `w0w1w5w6` `w0w1` Words available? `w0w1w5w6` `w0w1` Add to dictionary `w0w1w5w6` `w0w1` Pattern exists? `w0w1w5w6` `w0w1w5` Add word to pattern `w0w1w5w6` `w0w1w5` Words available? `w0w1w5w6` `w0w1w5` Add to dictionary `w0w1w5w6` `w0w1w5` Pattern exists? `w0w1w5w6` `w0w1w5w6` Add word to pattern `w0w1w5w6` `w0w1w5w6` Words available? `w0w1w5w6` `w0w1w5w6` Add to dictionary `w0w1w5w6` 1 `w0w1w5w6` `w0w1w5w6` Pattern exists? `w1w5w6` `w1w5` Take new pattern `w1w5w6` `w1w5` Add to dictionary `w1w5w6` `w1w5` Pattern exists? `w1w5w6` `w1w5w6` Add word to pattern `w1w5w6` `w1w5w6` Words available? `w1w5w6` `w1w5w6` Add to dictionary `w1w5w6` 1 `w1w5w6` `w1w5w6` Pattern exists? `w5w6` `w5w6` Take new pattern `w5w6` `w5w6` Words available? `w5w6` `w5w6` Add to dictionary `w5w6` `w5w6` Pattern exists? Take new pattern Words available? Exit

## Algorithm Pseudo Code

```ConstructPatterns(src, delimiters, n, fixed)
{
des = AllocateBuffer()
Copy(des, src)
dic = InitializePatternsDictionary()

pattern = InitializeNewPattern(des)
While(des)
{
node = dic.Insert(pattern)
if(!fixed AND node.IsRepeated)
else
pattern = InitializeNewPattern(des)
UpdateBuffer(des)
}
}```

## Code Description

### ConstructPatterns

This function receives the input buffer, copies it to a destination buffer, and parses it to add found patterns to the dictionary. The constructed dictionary is a binary tree template `CBinaryTree<CPattern, CPattern*, int, int> m_alpDic` with a key of type `CPattern`.

```void CPatternAlaysis::ConstructPatterns(BYTE *pSrc, int nSrcLen,
LPCSTR lpcsDelimiters /*= NULL*/,
int nMinPatternWords /*= 2*/,
bool bFixedNGram /*= false*/)
{
...
// allocate destination buffer
...
...
...
// initialize dictionary
m_alpDic.RemoveAll();
CBinaryTreeNode<CPattern, int>* pNode = m_alpDic.Root;
// left m_alpDic Samples points to the source buffer
int nPrevLength;
CPattern node(m_pDes, GetPatternLength(
m_pDes, nPrevLength, nMinPatternWords));
// scan the input buffer
while(node.m_pBuffer < m_pDes+nDesLen)
{
pNode = m_alpDic.Insert(&node, -1, pNode);
pNode->Key.m_nFrequency = pNode->Count;
if(bFixedNGram == false && pNode->Count > 1)
// (repeated pattern), increment node length
// by a new word length
else
{   // initialize node to next entity
node.m_pBuffer += nPrevLength;
node.m_nLength = GetPatternLength(node.m_pBuffer,
nPrevLength, nMinPatternWords);
// initialize binary tree search root
pNode = m_alpDic.Root;
}
}
}```

Note: The first good point in this function is that it allocates one buffer for the dictionary and all dictionary nodes point to their start buffer, keeping their buffer length in the class `CPattern`. So, no allocation or reallocation is done during the algorithm.

```class CPattern
{
public:
CPattern()    {}
CPattern(BYTE* pBuffer, int nLength)
{
m_pBuffer = pBuffer, m_nLength = nLength;
}
CPattern(const CPattern& buffer)
{
*this = buffer;
}

public:
BYTE* m_pBuffer;
int m_nLength;
int m_nFrequency;
inline int compare(const CPattern* buffer);
{    ...  }
inline void operator=(const CPattern* buffer)
{
m_pBuffer = buffer->m_pBuffer;
m_nLength = buffer->m_nLength;
}
};```

The function does the steps of the pseudo code. The second good point is the usage of a binary tree `CBinaryTree` to keep the dictionary, with a very good trick here:

The function `Insert()` of the tree takes a third parameter to start the search from. In normal cases, this parameter should be the tree `Root`. However, if a pattern is found and a new word is added to it, then we can start the search for the pattern from the current node, as it must be under current node. This is because it is only the previous pattern plus a new word.

In the case of a new pattern, we should start the search from the tree root, so we have this line at the bottom of the function:

```// initialize binary tree search root
pNode = m_alpDic.Root;```

### GetPatterns

This function doesn't construct patterns. It just retrieves the constructed patterns with three types of sort: Alphabetical, Frequency and Pattern length. The returned patterns are stored in a vector of patterns (`OUT vector<CPattern*>& vPatterns`).

1. #### Alphabetical

```CBinaryTreeNode<CPattern, int>* pAlpNode = m_alpDic.Min(m_alpDic.Root);
while(pAlpNode)
{
if(pAlpNode->Count > 1 || !bIgnoreUniquePatterns)
// ignore unique pattern
vPatterns.push_back(&pAlpNode->Key);
pAlpNode = m_alpDic.Successor(pAlpNode);
}```
2. #### Frequency

```// construct a new dictionary to sort stored patterns
// depending on frequency
CBinaryTree<CValue<int>, int, vector<CPattern*>,
vector<CPattern*>* >  displayDic;
CBinaryTreeNode<CPattern, int>* pAlpNode = m_alpDic.Min(m_alpDic.Root);
while(pAlpNode != NULL)
{
if(pAlpNode->Count >    1 || !bIgnoreUniquePatterns)
// ignore unique pattern
displayDic.Insert(pAlpNode->Count/*frequency*/)->
Data.push_back(&pAlpNode->Key);
pAlpNode = m_alpDic.Successor(pAlpNode);
}
// iterate through the binary tree to get sorted pattern
// (depend on frequency)
CBinaryTreeNode<CValue<int>, vector<CPattern*>* pNode =
displayDic.Max(displayDic.Root);
while(pNode)
{
for(vector<CPattern*>::iterator i = pNode->Data.begin(),
end = pNode->Data.end(); i != end; i++)
vPatterns.push_back(*i);
pNode = displayDic.Predecessor(pNode);
}```
3. #### Pattern length

```// construct a new dictionary to sort stored patterns
// depending on Pattern length
CBinaryTree<CValue<int>, int, vector<CPattern*>,
vector<CPattern*>* >  displayDic;
CBinaryTreeNode<CPattern, int>* pAlpNode = m_alpDic.Min(m_alpDic.Root);
while(pAlpNode != NULL)
{
if(pAlpNode->Count >    1 || !bIgnoreUniquePatterns)
// ignore unique pattern
displayDic.Insert(pAlpNode->Key.m_nLength/*length*/)->
Data.push_back(&pAlpNode->Key);
pAlpNode = m_alpDic.Successor(pAlpNode);
}
// iterate through the binary tree to get sorted pattern
// (depend on Pattern length)
CBinaryTreeNode<CValue<int>, vector<CPattern*>* pNode =
displayDic.Max(displayDic.Root);
while(pNode)
{
for(vector<CPattern*>::iterator i = pNode->Data.begin(),
end = pNode->Data.end(); i != end; i++)
vPatterns.push_back(*i);
pNode = displayDic.Predecessor(pNode);
}```

### GetPatternCount

This function retrieves the stored patterns count.

```int CPatternAlaysis::GetPatternCount()
{
return m_alpDic.Count;
}```

## Points of Interest

1. #### Algorithm Accuracy

The algorithm doesn't give accuracy about pattern frequency in the case of variable length patterns (not n-gram with fixed n). That is because the algorithm constructs patterns while parsing the sequence and checks each constructed pattern with the dynamic dictionary. So, if any pattern is first added to the dictionary, a new pattern is constructed starting from the second word of the previous pattern with length n (min pattern length).

2. #### Cross-Document Co-reference

Cross-Document Co-reference is the process of finding a relation between documents. In other words, we can say that two documents are related if the two documents contain similar patterns. This subject is studied in many articles, but I found that the best one is "A Methodology for Cross-Document Coreference" by Amit Bagga and Alan W.Biermann. My algorithm may be helpful to generate patterns that can be taken to find a relation between patterns. The good point here is the very good speed of the algorithm, so it can be used for large numbers of documents like the web. However, directed patterns are better than random patterns to find documents' co-reference.

3. #### DIPRE: Dual Iterative Pattern Relation Expansion

This is an algorithm introduced by Sergey Brin to collect related information from scattered web sources. I like this idea very much and invite all of you to read his article and search the web for its implementation or even its flowchart. My algorithm can't be used here, as it collects patterns without any guided information about retrieved patterns. However, the FIPRE algorithm is a semi-directed algorithm, as it guides the initial pattern search with regular expressions to identify the required pattern, like "book title" and "author name." Alternatively, if the algorithm collects mails, it will include all regular expressions for mails like that:

```[^ \:\=@;,,\$\$**++\t\r\n""'<>/\\%??()&]+@[Hh]otmail.com
[^ \:\=@;,,\$\$**++\t\r\n""'<>/\\%??()&]+@[Yy]ahoo.com
[^ \:\=@;,,\$\$**++\t\r\n""'<>/\\%??()&]+@(AOL|aol|Aol).com```

• 10/09/2007: Posted version v0.9000
• 23/09/2007: Updated the source file vector.cpp to initialize the vector buffer with zeros
• 31/10/2007: Updated the header file vector.h to solve "insert" function bug

## Thanks to...

God

 Hatem Mostafa Other Egypt Member

Votes of 3 or less require a comment

 Search this forum Profile popups    Spacing RelaxedCompactTight   Noise Very HighHighMediumLowVery Low   Layout Open AllThread ViewNo JavascriptPreview   Per page 102550
 First PrevNext
 My vote of 5 manoj kumar choubey 27 Mar '12 - 23:55
 How fast is your fast? Sanmayce 7 Jan '12 - 9:06
 Hi Mr.Mostafa, you said: "In this article, I introduce a very fast algorithm to extract text patterns from large size text..." Two questions here: How fast is your fast? I see no benchmarks nor phrases per second rate info. Also 'large', what is large, I am speaking mostly of size of ripped n-grams not the incoming texts. I myself have some ideas of really heavy n-gramming: http://www.sanmayce.com/Downloads/index.html#Leprechaun[^] Looking at article's date, 4 years are a lot of days, do you plan to enhance your approach, that is, is the project abandoned? In my view the n-gram ripping and searching is more than crucial and mostly indispensable when it comes to phrase analysis. Regards Get down get down get down get it on show love and give it up What are you waiting on? Sign In·View Thread·Permalink
 Article "Fast LZW compression" not found JohnPool 23 Sep '11 - 3:58
 Re: Article "Fast LZW compression" not found Hatem Mostafa 24 Sep '11 - 0:59
 Sir,   Suddenly I received this message from code project from 1 year: !!!!   ```Hi Hatem Mostafa,     Thanks very much for your contribution to The Code Project. However, your article 'Fast LZW Compression Using Binary Tree' in the Algorithms & Recipes section at   http://www.codeproject.com/KB/recipes/LZW.aspx has been deleted because it copies in whole or part the material in the article at [http://marknelson.us/1989/10/01/lzw-data-compression/] without giving due credit to that article. We take plagiarism seriously - please respect the rights of others.   If the article was only a work in progress and you intended to finish it at a later date we ask that you submit articles that are finished and ready for publishing.   If you have any questions regarding our submission policy please see our submission guidelines at http://www.codeproject.com/info/Submit.aspx.   Regards, Sean Ewington   The Code Project http://www.codeproject.com```   After 4 year of submitting the article they discoverd that, I tried to send to the admin many times, but he ignored me.   Thanks for care, Hatem Mostafa Sign In·View Thread·Permalink
 Re: Article "Fast LZW compression" not found Joao Araujo 10 Jan '12 - 8:50
 very strange this message about no reference i have a copy yor lzw article in 2008 and in this a reference to marknelson are there.. Sign In·View Thread·Permalink
 Re: Article "Fast LZW compression" not found Hatem Mostafa 11 Jan '12 - 20:37
 Sir,   Please, if you have a copy of my LZW article send it to me, as I don't have a backup of it.   My mail: hatemmostafa@hotmail.com   Many thanks, Hatem Sign In·View Thread·Permalink
 My vote of 5 RedDK 1 Jun '11 - 9:51
 ~CPatternAlaysis(); // had to add this desctructor declaration to PE.h to avoid VS2010 compile error C2600 wycoder 7 Apr '11 - 1:25
 I opened up Patterns.sln in Visual Studio 2010 and the project couldn't compile because of the following error:   ```error C2600: 'CPatternAlaysis::~CPatternAlaysis' : cannot define a compiler-generated special member function (must be declared in the class first) .\patterns_src\pe.cpp 15 1 Patterns ```   I'm guessing that this error would also happen in VS2008.   I simply added a line declaring the destructor in PE.h:   ```~CPatternAlaysis(); // had to add this desctructor declaration to PE.h (line 51) ``` Sign In·View Thread·Permalink
 C# implementation dde 21 Jul '09 - 16:52
 [Message Removed] Katekortez 25 Oct '08 - 9:30
 C# implementation navbas 19 Sep '08 - 6:54
 Hi is there any C# implementation of this algorithm availble... or any suggestion of how to use it in c# Sign In·View Thread·Permalink
 Re: C# implementation dde 21 Jul '09 - 16:44
 i could use a c# version too. in fact i am looking for something like this to port from c# to f# Sign In·View Thread·Permalink
 Born Analyst!! vamsidhar sunkari 22 Feb '08 - 6:15
 Re: Born Analyst!! Hatem Mostafa 7 Aug '11 - 11:59
 I am still remeber this message and so proud with your words. I have posted a new article: Real-time Feed Distribution using Shared Queue[^] I hope u find a time review it.   Many thanks Hatem Sign In·View Thread·Permalink
 Great! Abu Abdillah 30 Dec '07 - 8:27
 missing n+1 length patterns? b-yond 18 Nov '07 - 6:58
 I may be missing something, but why store the n+1 length patterns if we're not going to quantify them correctly?   In the dictionary example, the algorithm counts the pattern "w0w1w5w6" once, but there are in fact two occurrences of this pattern. It seems that if there was a pattern "w0w1w5w6w7", it would miss it altogether. If we don't care about patterns with length greater than n, why are we counting any at all?   Thanks for any clarification. Great Algorithm by the way.   -BryanC Sign In·View Thread·Permalink
 Re: missing n+1 length patterns? Hatem Mostafa 18 Nov '07 - 8:24
 Sir,   My algorithm for pattern extraction depends on LZW compression algorithm which scans input buffer only once and extracts repeated sequences through genrating a dictionary at parsing time.   If u want more accurate patterns frequency we should parse input again with the same dictionary more times tell a certain saturation limit.   Any way it is a research topic and my algorithm may be a good chance to find a very fast pattern extraction technique.   Thank Hatem Sign In·View Thread·Permalink
 Re: missing n+1 length patterns? b-yond 18 Nov '07 - 15:14
 Hey, thanks Hatem. You're right, this is an excellent fast pattern extraction technique. I appreciate you clarifying that for me. I was thinking more along the lines of just defining and quantifying the patterns, like for keyword extraction.   Great work!   -BryanC Sign In·View Thread·Permalink
 Re: missing n+1 length patterns? yulin11 3 Dec '07 - 17:42
 Dear all. I am using Visual C++ 6.0 with windows XP. I created project. MDI project with MFC. when the project completed I run it , but the document is not enabled for input from keyboard. can u help me for this issu please? and tell me how can I type in this document?     allooba Sign In·View Thread·Permalink
 Nice Paul Conrad 4 Nov '07 - 14:06
 Very nice and I couldn't believe how quick it was until I tried it   "Real programmers just throw a bunch of 1s and 0s at the computer to see what sticks" - Pete O'Hanlon Sign In·View Thread·Permalink
 Do you see a use for code breaking? D N Harris 1 Oct '07 - 2:05
 Re: Do you see a use for code breaking? !!! Hatem Mostafa 1 Oct '07 - 22:02
 Sir,   That is a good question and I expected to receive it any time (regardless its title). U r right, text delimiters affect in extracted patterns so u have two options: - put all ur delimiters (type of encryption used) in the edit of the delimiters to ignore them before parsing the text - leave the edit empty to take them as a part of patterns so they will affect the extracted patterns.   The solution to this problem is to enhance `ConstractPatterns` function to use to type of delimiters: - one to avoid or remove - the other to use as a text separator as a normal use of the delimiters   Sorry for this conflict, I'll take it in consideration next update.   Thanks Hatem Sign In·View Thread·Permalink
 Re: Do you see a use for code breaking? D N Harris 2 Oct '07 - 2:53
 Thank you for the thoughtful response; the moment we programmatically try to kill two birds with one stone, we start to spin like a top.   You have done the hard work.   Thanks again,   David Sign In·View Thread·Permalink
 very good ! gogac 26 Sep '07 - 11:25
 thank you for a good article !   just a couple of "wish list" items: - would be nice to have an option to apply it to characters as well (not only words) - and an ability to persist/reload the dictionary/data and continue with new/other file(s) later Sign In·View Thread·Permalink
 Last Visit: 31 Dec '99 - 18:00     Last Update: 18 May '13 - 8:54 Refresh 12 Next »