正则表达式用于缺少一个字母的单词以及两个顺序颠倒的字母



我想详细说明一个涵盖以下场景的正则表达式:

搜索的词是"土豆"。

如果用户搜索"土豆",它匹配(他打字很快,"o"指比"t"指快。(完成)

如果用户搜索"ptato"(他忘记了一个字母),它就会匹配。(完成)

以我对正则表达式的了解,我可以走得更远:

(?=[potato]{5,6})p?o?t?a?t?o?

这样做的问题在于它匹配了相反的单词,如"otatop",这有点聪明,但有点奇怪,以及"ooooo",这是完全不可取的。所以我不描述我不想要的东西。

我不希望重复的字母与"ooooo","ooopp"等匹配(无法)

顺便说一下,我使用的是 C#。

不要使用正则表达式。

最好的解决方案是简单的解决方案。只有 11 个可能的不精确匹配,因此只需枚举它们:

List<string> inexactMatches = new List<string> {
"otato", "ptato", "poato", "potto", "potao", "potat",
"optato", "ptoato", "poatto", "pottao", "potaot"};
...
bool hasInexactMatch = inexactMatches.Contains(whatever);

输入这些内容花了不到一分钟的时间;使用简单的特定解决方案,而不是尝试做一些疯狂的正则表达式,这将花费您数小时才能找到和调试。

如果你要坚持使用正则表达式,这里有一个有效的:

otato|ptato|poato|potto|potao|potat|optato|ptoato|poatto|pottao|potaot

再次:越简单越好。


现在,人们可能会认为您想用比"土豆"更多的词来解决这个问题。 在这种情况下,你可能已经这么说了 - 但无论如何,我们可以提出一些简单的解决方案。

首先,让我们枚举目标字符串中省略一个字母的所有字符串。 字符串是IEnumerable<char>所以让我们解决一般问题:

static IEnumerable<T> OmitAt<T>(this IEnumerable<T> items, int i) =>
items.Take(i).Concat(items.Skip(i + 1));

枚举序列两次有点粗暴,但我不会强调它。 现在让我们为字符串创建一个特定的版本:

static IEnumerable<string> Omits(this string s) =>
Enumerable
.Range(0, s.Length)
.Select(i => new string(s.OmitAt(i).ToArray()));

伟大。现在我们可以说"frob".Omits()并回到rob, fob, frb, fro.

现在让我们进行掉期。再次,先解决一般问题:

static void Swap<T>(ref T x, ref T y)
{
T t = x;
x = y;
y = t;
}
static T[] SwapAt<T>(this IEnumerable<T> items, int i)
{
T[] newItems = items.ToArray();
Swap(ref newItems[i], ref newItems[i + 1]);
return newItems;
}

现在我们可以为字符串求解它:

static IEnumerable<string> Swaps(this string s) =>
Enumerable
.Range(0, s.Length - 1)
.Select(i => new string(s.SwapAt(i)));

现在我们完成了:

string source = "potato";
string target = whatever;
bool match = 
source.Swaps().Contains(target) || 
source.Omits().Contains(target);

简单易行。使用简单、直接、正确的算法解决一般问题,这些算法可以组合成更大的解决方案。我的算法都没有超过三行,很容易看出它们是正确的。

这里选择的武器是相似性(或距离)匹配算法。 比较相似性算法可以很好地概述最常见的距离指标/算法。

问题是没有单一的最佳指标。选择取决于输入类型、精度要求、速度、资源可用性等。然而,比较算法可能会很混乱。

两个最常建议的指标是Levenshtein距离和Jaro-Winkler:

  • Levenshtein 距离提供了两个字符串之间的相似性度量,可以说比其他一些指标更不宽容,并且更直观地理解。(Levenshtein 距离有修改版本,例如 Damerau-Levenshtein 距离,其中包括换位,可能更适合您的用例。

  • 有人声称Jaro-Winkler提供了两个字符串之间的相似性度量,允许字符换位在一定程度上调整常见前缀的权重,距离是"目前可用的最高性能和最准确的近似字符串匹配算法之一[Cohen等人],[Winkler]。然而,选择仍然在很大程度上取决于用例,人们不能从特定的研究中得出一般结论,例如名称匹配Cohen等人,2003年。

你可以在 NuGet 上找到大量程序包,这些程序包为你提供各种相似性算法(a、b、c)、模糊匹配、语音等,以将此功能添加到你的网站或应用。

模糊匹配也可以直接在数据库层上使用。Levenshtein距离的实现可以在大多数数据库系统(例如MySQL,SQL Server)中找到,或者已经内置(Oracle,PostgreSQL)。

根据您的确切用例,您还可以使用基于云的解决方案(即使用基于 AWS、Azure 等的微服务或自己的微服务)来获得类似自动建议的模糊搜索/匹配。

这样做最容易

static void Main(string[] args)
{
string correctWord = "Potato";
string incorrectSwappedWord = "potaot";
string incorrectOneLetter = "ptato";
// Returns true
bool swapped = SwappedLettersMatch(correctWord, incorrectSwappedWord);
// Returns true
bool oneLetter = OneLetterOffMatch(correctWord, incorrectOneLetter);
}
public static bool OneLetterOffMatch(string str, string input)
{
int ndx = 0;
str = str.ToLower();
input = input.ToLower();
if (string.IsNullOrWhiteSpace(str) || string.IsNullOrWhiteSpace(input))
{
return false;
}
while (ndx < str.Length)
{
string newStr = str.Remove(ndx, 1);
if (input == newStr)
{
return true;
}
ndx++;
}
return false;
}
public static bool SwappedLettersMatch(string str, string input)
{
if (string.IsNullOrWhiteSpace(str) || string.IsNullOrWhiteSpace(input))
{
return false;
}
if (str.Length != input.Length)
{
return false;
}
str = str.ToLower();
input = input.ToLower();
int ndx = 0;
while (ndx < str.Length)
{
if (ndx == str.Length - 1)
{
return false;
}
string newStr = str[ndx + 1].ToString() + str[ndx];
if (ndx > 0)
{
newStr = str.Substring(0, ndx) + newStr;
}
if (str.Length > ndx + 2)
{
newStr = newStr + str.Substring(ndx + 2);
}
if (newStr == input)
{
return true;
}
ndx++;
}
return false;
}

OneLetterOffMatch会返回 true 是有一个匹配只缺少一个字符。 当只有两个字母被交换时,SwappedLettersMatch将返回 true。这些函数不区分大小写,但如果您想要区分大小写的版本,只需删除对.ToLower()的调用。

最新更新