如何String.Contains()C#中的模糊方式?

我有一个我想在过滤时搜索的人员列表。 每次用户输入搜索字符串时,都会应用过滤。

需要考虑两个挑战:

  1. 用户可以输入部分名称
  2. 用户可能会输入错误

第一个是通过搜索子字符串简单解决的,例如String.Contains()。 第二个可以通过使用模糊实现来解决(例如https://fuzzystring.codeplex.com )

但我不知道如何同时掌握这两个挑战。

例如:我想在进入以下任何一个时找到“Martin Fowler博士”的人:

  • “马丁”
  • “Fawler”
  • “Marten Fauler”

我想我需要编写一个“FuzzyContains()”逻辑,它可以满足我的需求并具有可接受的性能。 任何建议如何开始?

似乎是Levenshtein距离算法( 几十个C#实现之一 )的工作。

您为此算法提供两个字符串(用户输入的字符串和列表中的一个字符串)。 然后计算从第一个字符串到第二个字符串必须替换,添加或删除的字符数。 然后,您可以从列表中获取距离小于或等于3的所有元素(例如)以查找简单的拼写错误。

如果你有这种方法,你可以这样使用它:

var userInput = textInput.Text.ToLower(); var matchingEmployees = EmployeeList.Where(x => x.Name.ToLower().Contains(userInput) || LevenshteinDistance.Compute(x.Name.ToLower(), userInput) <= 3) .ToList(); 

我修改了提出Levenshtein距离算法的Oliver回答 ,这不是这里的最佳选择,因为当只输入部分名称时计算的距离很大。 所以,我最终使用了由令人敬畏的FuzzyString Lib实现的最长公共子 序列 算法 。

 const int TOLERANCE = 1; string userInput = textInput.Text.ToLower(); var matchingPeople = people.Where(p => { //Check Contains bool contains = p.Name.ToLower().Contains(userInput); if(contains) return true; //Check LongestCommonSubsequence bool subsequenceTolerated = p.Name.LongestCommonSubsequence(userInput).Length >= userInput.Length - TOLERANCE; return subsequenceTolerated; }).ToList(); 

我之前已经完成了这个,并开始使用维基百科上列出的一些方法进行近似字符串匹配 。 当我完成后,我调整了我的算法的方式不是通用的,但在我的域中给了我更好的匹配。

如果您的整个字典在内存中并且不是太大,您可以简单地对字典中的每个成员应用匹配算法。 如果您的字典很大,这可能会过度使用您的资源,您将需要更好的算法。 您可能还想考虑使用数据库的全文搜索function。

在我的例子中,我迭代了我的字典中的每个字符串,比较“匹配运行”,即2个点用于2个字符匹配,3个用于3个字符匹配,最多8个字符匹配。 我跑了所有可能的对,三元组等等 – 对每个词典条目进行评分并选择最高得分的比赛。 容忍错别字,词序等,但计算成本昂贵 – 我的词典是几千个短语,所以这对我来说非常好。 这是Dice系数的修改版本。

也许你可以使用这个soundex实现: CodeProject soundex做的是比较两个字符串并以百分比计算“发音相似度”。 前一段时间我借助这个函数构建了一个搜索(PHP hasit内置)

在像python这样的其他语言中,我们有很酷的文本处理function,包括距离计算。 有一些算法,如Levenshtein ,计算两个字符串之间的模糊距离。 我在C#中看到了一些实现( 在这里 ),另一个模块是difflib , 这里有 。 这些算法的输出是一个数字。 越接近0越好。

我前段时间有一个上学项目,我们有一个文本框,学生可以搜索每个员工,学生与学校有关。 我们谈论的是几百人。 我们在Core i3处理器上使用的简单Linq查询非常快。 每次用户在文本框中键入内容时都会调用该查询。 在TextChanged事件中,我们调用了一个如下所示的查询:

 var resultData = EmployeeList.Where(x=>x.Name.ToLower().Contains(textInput.Text.ToLower())).ToList(); 

当然,只有在一个财产或成员中有“Martin Fowler博士”时,此逻辑才适用。

你有没有尝试蛮力? 最简单的方法是将搜索字符串与从头开始的目标字符串的子字符串进行匹配,然后从所有匹配中获取最接近的匹配。

但是从性能视图来看这可能是不可接受的。