There may be a better way, but this is how I would go about solving the problem.
You will need to create a tree structure that contains your dictionary of words to be replaced. The class may be something like:
public class Node
{
public Dictionary<char, Node> Children;
public bool IsWord;
}
Using a dictionary for the Children may not be the best choice, but it provides the easiest example here. Also, you will need a constructor to initialize the Children field. The IsWord field is used to deal with the possibility that a redacted "word" may be the prefix of another redacted "word". For example, if you want to remove both "red" and "redress".
You will build the tree from each character in each of the replacement words. For example:
public void AddWord ( string word )
{
// NOTE: this assumes word is non-null and contains at least one character...
Node currentNode = Root;
for (int iIndex = 0; iIndex < word.Length; iIndex++)
{
if (currentNode.Children.ContainsKey(word[iIndex])))
{
currentNode = currentNode.Children[word[iIndex];
continue;
}
Node newNode = new Node();
currentNode.Children.Add(word[iIndex], newNode);
currentNode = newNode;
}
// finished, mark the last node as being a complete word..
currentNode.IsWord = true;
}
You'll need to deal with case sensitivity somewhere in there. Also, you only need to build the tree once, afterwards you can use it from any number of threads without worrying about locking because you will be only reading from it. (Basically, I'm saying: store it in a static somewhere.)
Now, when you are ready to remove words from your string you will need to do the following:
- Create a StringBuilder instance to store the result
- Parse through your source string, looking for the start and stop of a "word". How you define "word" will matter. For simplicity I would suggest starting with
Char.IsWhitespace as defining word separators.
- Once you have determined that a range of character is a "word", starting from the root of the tree, locate the child node associated with the first character in "word".
- If you do not find a child node, the entire word is added to the
StringBuilder
- If you find a child node, you continue with the next character matching against Children of the current node, until you either run out of characters or out of nodes.
- If you reach the end of the "word", check the last node's
IsWord field. If true the word is excluded, do not add it to the StringBuilder. If IsWord is false, the word is not replaced and you add it to the StringBuilder
- Repeat until you have exhausted the input string.
You will also need to add word separators to the StringBuilder, hopefully that will be obvious as you parse the input string. If you are careful to only use the start and stop indices within the input string, you should be able to parse the entire string without creating any garbage strings.
When all of this is done, use StringBuilder.ToString() to get your final result.
You may also need to consider Unicode surrogate codepoints, but you can probably get away without worrying about it.
Beware, I typed this code here directly, so syntax errors, typos and other accidental misdirections are probably included.