我有一个200多个单词的列表,这些单词在网站上是不允许的。下面的string.Replace
方法需要大约80毫秒。如果我将s < 1000
增加10.00到s < 10,000
,则此延迟会增加到约834毫秒,增加10.43。我担心这个函数的可伸缩性,特别是如果列表的大小增加。我被告知字符串是不可变的,text.Replace()
在内存中创建200个新字符串。是否有类似于Stringbuilder
的内容?
List<string> FilteredWords = new List<string>();
FilteredWords.Add("RED");
FilteredWords.Add("GREEN");
FilteredWords.Add("BLACK");
for (int i = 1; i < 200; i++)
{ FilteredWords.Add("STRING " + i.ToString()); }
string text = "";
//simulate a large dynamically generated html page
for (int s = 1; s < 1000; s++)
{ text += @"Lorem ipsum dolor sit amet, minim BLACK cetero cu nam.
No vix platonem sententiae, pro wisi congue graecis id, GREEN assum interesset in vix.
Eum tamquam RED pertinacia ex."; }
// This is the function I seek to optimize
foreach (string s in FilteredWords)
{ text = text.Replace(s, "[REMOVED]"); }
答案 0 :(得分:2)
如果您希望大多数文本比扫描整个文本相对更好,首先匹配单词可能是更好的方法。您还可以同时对单词文本进行标准化,以捕获一些标准替换。
即。扫描字符串通过匹配单个单词(即正则表达式,如"\w+"
),而不是每个检测到的单词查找(可能标准化的值)在要替换的单词的字典中。
您可以先扫描一下以获取“要替换的单词”列表,然后再单独替换单个单词,或者同时扫描并构建生成的字符串(使用StringBuilder
或StreamWriter
,显然不是String.Concat
/ +
)。
注意:Unicode提供了大量优秀的字符,因此不要指望您的努力非常成功。即试着在下面的文字中找到“酷”:“你是сооl”。
示例代码(依赖Regex.Replace进行标记化并构建字符串,并HashSet
进行匹配)。
var toFind = FilteredWords.Aggregate(
new HashSet<string>(), (c, i) => { c.Add(i); return c;});
text = new Regex(@"\w+")
.Replace(text, m => toFind.Contains(m.Value) ? "[REMOVED]" : m.Value));
答案 1 :(得分:2)
使用StringBuilder.Replace
并尝试将其作为批处理操作。也就是说,您应该尝试仅创建一次StringBuilder
,因为它有一些开销。它不一定快得多,但它的内存效率会更高。
您也应该只进行一次卫生,而不是每次请求数据。如果您正在从数据库中读取数据,那么在将数据插入数据库时应该考虑将其清理一次,因此在阅读并将其显示到页面时,可以做的工作较少。
答案 2 :(得分:1)
可能有更好的方法,但这就是解决问题的方法。
您需要创建一个包含要替换的单词词典的树结构。这个班可能是这样的:
public class Node
{
public Dictionary<char, Node> Children;
public bool IsWord;
}
为儿童使用字典可能不是最佳选择,但它提供了最简单的示例。此外,您还需要一个构造函数来初始化Children
字段。 IsWord
字段用于处理编辑的“单词”可能是另一个编辑的“单词”的前缀的可能性。例如,如果要删除“红色”和“补救”。
您将从每个替换单词中的每个字符构建树。例如:
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;
}
你需要在那里的某处处理区分大小写。此外,您只需要构建一次树,之后您可以从任意数量的线程中使用它而不必担心锁定,因为您只会从中读取它。 (基本上,我说的是:将它存放在静止的地方。)
现在,当您准备从字符串中删除单词时,您需要执行以下操作:
Char.IsWhitespace
开始定义单词分隔符。StringBuilder
IsWord
字段。如果true
该词被排除在外,请不要将其添加到StringBuilder
。如果IsWord
为false
,则不会替换该字词,并将其添加到StringBuilder
您还需要在StringBuilder
中添加单词分隔符,希望在解析输入字符串时这一点很明显。如果你小心只在输入字符串中使用start和stop索引,你应该能够解析整个字符串而不创建任何垃圾字符串。
完成所有这些操作后,请使用StringBuilder.ToString()
获取最终结果。
您可能还需要考虑Unicode代理代码点,但您可以可能离开而不必担心它。
请注意,我直接在此输入此代码,因此可能包含语法错误,拼写错误和其他意外误导。
答案 3 :(得分:0)
真正的正则表达式解决方案是:
var filteredWord = new Regex(@"\b(?:" + string.Join("|", FilteredWords.Select(Regex.Escape)) + @")\b", RegexOptions.Compiled);
text = filteredWord.Replace(text, "[REMOVED]");
我不知道这是否更快(但请注意,它也只替换整个单词)。