编辑:我收到了一些非常好的建议,我会尝试通过它们并在某些时候接受答案
我有一个很大的字符串列表(800k),我想尽快过滤掉不需要的单词列表(最终亵渎,但可能是任何东西)。
我最终希望看到的结果是
等列表Hello,World,My,Name,Is,Yakyb,Shell
会变成
World,My,Name,Is,Yakyb
经过检查后
Hell,Heaven.
到目前为止,我的代码是
var words = items
.Distinct()
.AsParallel()
.Where(x => !WordContains(x, WordsUnwanted));
public static bool WordContains(string word, List<string> words)
{
for (int i = 0; i < words.Count(); i++)
{
if (word.Contains(words[i]))
{
return true;
}
}
return false;
}
这目前需要大约2.3秒(9.5 w / o并行)来处理800k字,这对于一次性来说并不是什么大不了的事。但是,学习过程是否有更快的处理方式?
不需要的单词列表长100个字
没有一个词包含标点符号或空格
答案 0 :(得分:1)
几件事
改动1(漂亮又简单): 通过在Distinct方法上使用HashSet,我能够加速运行(分数)。
var words = new HashSet<string>(items) //this uses HashCodes
.AsParallel()...
改变2(跟我一起;)): 关于@Tim的评论,包含可能无法为您提供足够的搜索黑名单词。例如 Takeshita 是街道名称。
你已经确定你想要这个词的有限状态(又名Stemmed)。例如,对于苹果,我们会将其视为Apple。为此,我们可以使用诸如Porter Stemmer等词干算法。
如果我们要干一个字然后我们可能不需要做包含(x),我们可以使用等于(x)或甚至更好地比较HashCodes(最快的方式)。
var filter = new HashSet<string>(
new[] {"hello", "of", "this", "and", "for", "is",
"bye", "the", "see", "in", "an",
"top", "v", "t", "e", "a" });
var list = new HashSet<string> (items)
.AsParallel()
.Where(x => !filter.Contains(new PorterStemmer().Stem(x)))
.ToList();
这将比较其哈希码上的字词 int == int 。
使用stemmer并没有减慢速度,因为我们使用HashSet对其进行补充(对于过滤后的列表,bigO为1)。这返回了更大的结果列表。
我正在使用位于Lucene.Net代码中的Porter Stemmer,这不是线程安全因此我们每次新建一个
Alteration 2的问题,Alteration 2a:与大多数自然语言处理一样,并不简单。
时会发生什么通过论坛,他们利用人类来弥补这些差距。
或者引入了白名单(假设您提到了bigO,我们可以说它的性能将达到2n ^ 2,因为我们为每个项目做了2个列表,不要忘记删除领先的人,如果我没记错的话你会留下n ^ 2,但我的bigO上有点生锈)
答案 1 :(得分:1)
更改您的WordContains
方法以使用单个Aho-Corasick搜索而不是~100包含调用(当然只需初始化Aho-Corasick搜索树一次)。
您可以在http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=12383找到一个开源实施。
在StringSearch
课程开始后,您将为每个800k字符串调用方法public bool ContainsAny(string text)
。
无论你的不受欢迎的词汇列表有多长,一次通话都会花费O(字符串的长度)时间。
答案 2 :(得分:0)
尝试使用名为Except
的方法。
http://msdn.microsoft.com/en-AU/library/system.linq.enumerable.except.aspx
var words = new List<string>() {"Hello","Hey","Cat"};
var filter = new List<string>() {"Cat"};
var filtered = words.Except(filter);
还怎么样:
var words = new List<string>() {"Hello","Hey","cat"};
var filter = new List<string>() {"Cat"};
// Perhaps a Except() here to match exact strings without substrings first?
var filtered = words.Where(i=> !ContainsAny(i,filter)).AsParallel();
// You could experiment with AsParallel() and see
// if running the query parallel yields faster results on larger string[]
// AsParallel probably not worth the cost unless list is large
public bool ContainsAny(string str, IEnumerable<string> values)
{
if (!string.IsNullOrEmpty(str) || values.Any())
{
foreach (string value in values)
{
// Ignore case comparison from @TimSchmelter
if (str.IndexOf(value, StringComparison.OrdinalIgnoreCase) != -1) return true;
//if(str.ToLowerInvariant().Contains(value.ToLowerInvariant()))
// return true;
}
}
return false;
}
答案 3 :(得分:0)
啊,根据“坏”列表中的匹配过滤单词。这是一个克隆问题,已经测试了许多程序员的完整性。来自斯肯索普的朋友写了一篇关于它的论文。
你真正想要避免的是一个在O(lm)中测试单词的解决方案,其中l是要测试的单词的长度,m是坏单词的数量。为了做到这一点,你需要一个解决方案,而不是循环坏词。我曾经认为正则表达式可以解决这个问题,但我忘了典型的实现有一个内部数据结构,每次交替都会增加。正如其他解决方案之一所说,Aho-Corasick就是这样做的算法。标准实现找到所有匹配项,因为你可以在第一场比赛中拯救,所以你的比赛会更有效率。我认为这提供了一个理论上最优的解决方案。
答案 4 :(得分:0)
我有兴趣看看我是否能想出一个更快的方法 - 但我只管理了一点优化。那就是检查在另一个字符串中出现的字符串的索引,因为它首先看起来比'contains'略快,然后让你指定不区分大小写(如果这对你有用)。
下面是我写的测试类 - 我使用了&gt; 100万字,并且在所有情况下使用区分大小写的测试。它测试你的方法,也是我试图在运行中建立的正则表达式。你可以亲自尝试一下,看看时间;正则表达式的工作速度不如您提供的方法快,但我可能会错误地构建它。我之前使用(?i)(word1 | word2 ...)来指定正则表达式中的不区分大小写(我很想知道如何优化它 - 它可能会遇到经典的回溯问题!)。
随着更多“不需要的”词语被添加,搜索方法(无论是正则表达式还是提供的原始方法)似乎进展缓慢。
无论如何 - 希望这个简单的测试可以帮助你:
class Program
{
static void Main(string[] args)
{
//Load your string here - I got war and peace from project guttenburg (http://www.gutenberg.org/ebooks/2600.txt.utf-8) and loaded twice to give 1.2 Million words
List<string> loaded = File.ReadAllText(@"D:\Temp\2600.txt").Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries).ToList();
List<string> items = new List<string>();
items.AddRange(loaded);
items.AddRange(loaded);
Console.WriteLine("Loaded {0} words", items.Count);
Stopwatch sw = new Stopwatch();
List<string> WordsUnwanted = new List<string> { "Hell", "Heaven", "and", "or", "big", "the", "when", "ur", "cat" };
StringBuilder regexBuilder = new StringBuilder("(?i)(");
foreach (string s in WordsUnwanted)
{
regexBuilder.Append(s);
regexBuilder.Append("|");
}
regexBuilder.Replace("|", ")", regexBuilder.Length - 1, 1);
string regularExpression = regexBuilder.ToString();
Console.WriteLine(regularExpression);
List<string> words = null;
bool loop = true;
while (loop)
{
Console.WriteLine("Enter test type - 1, 2, 3, 4 or Q to quit");
ConsoleKeyInfo testType = Console.ReadKey();
switch (testType.Key)
{
case ConsoleKey.D1:
sw.Reset();
sw.Start();
words = items
.Distinct()
.AsParallel()
.Where(x => !WordContains(x, WordsUnwanted)).ToList();
sw.Stop();
Console.WriteLine("Parallel (original) process took {0}ms and found {1} matching words", sw.ElapsedMilliseconds, words.Count);
words = null;
break;
case ConsoleKey.D2:
sw.Reset();
sw.Start();
words = items
.Distinct()
.Where(x => !WordContains(x, WordsUnwanted)).ToList();
sw.Stop();
Console.WriteLine("Non-Parallel (original) process took {0}ms and found {1} matching words", sw.ElapsedMilliseconds, words.Count);
words = null;
break;
case ConsoleKey.D3:
sw.Reset();
sw.Start();
words = items
.Distinct()
.AsParallel()
.Where(x => !Regex.IsMatch(x, regularExpression)).ToList();
sw.Stop();
Console.WriteLine("Non-Compiled regex (parallel) Process took {0}ms and found {1} matching words", sw.ElapsedMilliseconds, words.Count);
words = null;
break;
case ConsoleKey.D4:
sw.Reset();
sw.Start();
words = items
.Distinct()
.Where(x => !Regex.IsMatch(x, regularExpression)).ToList();
sw.Stop();
Console.WriteLine("Non-Compiled regex (non-parallel) Process took {0}ms and found {1} matching words", sw.ElapsedMilliseconds, words.Count);
words = null;
break;
case ConsoleKey.Q:
loop = false;
break;
default:
continue;
}
}
}
public static bool WordContains(string word, List<string> words)
{
for (int i = 0; i < words.Count(); i++)
{
//Found that this was a bit fater and also lets you check the casing...!
//if (word.Contains(words[i]))
if (word.IndexOf(words[i], StringComparison.InvariantCultureIgnoreCase) >= 0)
return true;
}
return false;
}
}