Ven*_*tus 6 c# regex text-processing
在我的项目中,我一般都使用文本.我发现预处理可能非常慢.所以我想问你是否知道如何优化我的代码.流程是这样的:
获取HTML页面 - >(以纯文本 - >词干 - >删除停用词) - >进一步文本处理
括号中有预处理步骤.该应用程序运行在大约10.265秒,但预处理需要9.18秒!这是预处理50个HTML页面的时间(不包括下载).
我使用HtmlAgilityPack库将HTML转换为纯文本.这很快.转换1个文档需要2.5ms,所以它相对比较好.
问题出现了.阻止一个文档需要120毫秒.不幸的是,那些HTML页面是波兰语.用C#编写的波兰语不存在词干.我知道只有2个免费使用Java编写:stempel和morfologic.我借助IKVM软件将stempel.jar预编译为stempel.dll.所以没有更多的事要做.
消除停用词也需要很多时间(1个文档约70毫秒).它是这样完成的:
result = Regex.Replace(text.ToLower(), @"(([-]|[.]|[-.]|[0-9])?[0-9]*([.]|[,])*[0-9]+)|(\b\w{1,2}\b)|([^\w])", " ");
while (stopwords.MoveNext())
{
string stopword = stopwords.Current.ToString();
result = Regex.Replace(result, "(\\b"+stopword+"\\b)", " ");
}
return result;
Run Code Online (Sandbox Code Playgroud)
首先,我删除所有数字,特殊字符,单词和双字母单词.然后在循环中删除停用词.大概有270个停用词.
有可能让它更快吗?
编辑:
我想要做的是删除所有不超过2个字母的单词.所以我想把所有特殊的字符(包括'.',',','?','!'等)数字,停止字样.我只需要用于数据挖掘的纯语言.
LBu*_*kin 15
迭代替换单词将成为您实现中的最大瓶颈.在每次迭代时,您必须扫描整个字符串以获取禁用词,然后替换操作必须分配一个新字符串并使用替换后的文本填充它.那不会很快.
一种更有效的方法是对字符串进行标记化并以流式方式执行替换.将输入划分为由适当的空格或分隔符分隔的单个单词.您可以逐步执行此操作,因此您无需分配任何额外的内存来执行此操作.对于每个单词(标记),您现在可以在停用词的哈希集中执行查找 - 如果找到匹配项,则在将最终文本流式化为单独文本时将替换它StringBuilder
.如果令牌不是停用词,只需将其流出StringBuilder
未经修改的.此方法应具有O(n)性能,因为它只扫描字符串一次并使用a HashSet
来执行禁用字查找.
以下是我期望表现更好的一种方法.虽然它不是完全流式传输(它使用String.Split()
分配了一系列附加字符串的数据),但它在一次传递中完成所有处理.优化代码以避免分配额外的字符串可能不会提供太多改进,因为您仍需要提取子字符串以执行与停用词的比较.
下面的代码返回一个排除所有停用词和单词的单词列表,结果是两个字母或更短的字母.它还对停用词使用不区分大小写的比较.
public IEnumerable<string> SplitIntoWords( string input,
IEnumerable<string> stopwords )
{
// use case-insensitive comparison when matching stopwords
var comparer = StringComparer.InvariantCultureIgnoreCase;
var stopwordsSet = new HashSet<string>( stopwords, comparer );
var splitOn = new char[] { ' ', '\t', '\r' ,'\n' };
// if your splitting is more complicated, you could use RegEx instead...
// if this becomes a bottleneck, you could use loop over the string using
// string.IndexOf() - but you would still need to allocate an extra string
// to perform comparison, so it's unclear if that would be better or not
var words = input.Split( splitOn, StringSplitOptions.RemoveEmptyEntries );
// return all words longer than 2 letters that are not stopwords...
return words.Where( w => !stopwordsSet.Contains( w ) && w.Length > 2 );
}
Run Code Online (Sandbox Code Playgroud)
好吧,我知道 SO 不是一个纯粹的论坛,也许我不应该回答我自己的问题,但我想分享我的结果。
最后,感谢你们,我成功地优化了我的文本预处理。首先,我从我的问题中简化了长表达(遵循乔什·凯利的回答):
[0-9]|[^\w]|(\b\w{1,2}\b)
它的作用与第一个相同,但非常简单。然后再次按照 Josh Kelley 的建议,我将这个正则表达式放入汇编中。我在这里找到了将表达式编译成程序集的好例子。我这样做是因为这个正则表达式被使用了很多很多次。在听了几篇有关编译正则表达式的文章后,这是我的决定。我在消除停用词后删除了最后一个表达式(这没有真正的意义)。
因此 12KiB 文本文件的执行时间约为 15 毫秒。这只是为了上面提到的表达。
最后一步是停用词。我决定对 3 个不同的选项进行测试(执行时间针对相同的 12KiB 文本文件)。
包含所有停用词并编译成程序集(mquander 的建议)。这里没有什么需要澄清的。
人们说这比正则表达式更快。所以对于每个停用词我都使用了string.Replace()
方法。需要进行多次循环才能得到结果:
LBushkin提出的方法。没什么可说的了。
我只能说哇。只需比较第一个和最后一个的执行时间即可!非常感谢LBushkin!