有一项任务 - 组织搜索不同语言的单词文本。同时,所有可能的 Unicode 的变音符号和其他乐趣都可以出现在单词中。
例如,有 2 艘船的名称 - GermanGroßer Kurfürst和 Polish Błyskawica。我想在文本中写下grosser或blyska找到这些标题。为了让这一切成为可能,我们使用了文本规范化功能,该功能既适用于搜索字符串,也适用于文本本身。她将被进一步讨论。
有一个有效的、经过验证的、防弹的解决方案。
public static string NormalizeUnicode(string text)
{
string stFormD = text.ToLower()
.Replace('\u00A0', ' ') // неразрывный пробел
.Replace("ß", "ss")
.Replace('ł', 'l')
.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder(stFormD.Length);
foreach (char c in stFormD)
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
sb.Append(c);
return sb.ToString().Normalize(NormalizationForm.FormC);
}
输出将分别给出grosser kurfurst和blyskawica。唯一的问题是它甚至可以以可接受的速度工作,但我希望它更快。
自从使用该解决方案(在英文 Internet 上找到)以来,已经过去了很长时间,在此期间,.NET 5 出现了它的跨度和其他 C# 8-9 的乐趣。我试图优化。
检查代码
public static string NormalizeUnicodeNew(string text)
{
ReadOnlySpan<char> stFormD = text.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder(stFormD.Length);
foreach (char c in stFormD)
{
switch (CharUnicodeInfo.GetUnicodeCategory(c))
{
case UnicodeCategory.UppercaseLetter:
sb.Append(char.ToLower(c));
break;
case not UnicodeCategory.NonSpacingMark:
if (c == 'ß')
sb.Append("ss");
else
sb.Append(c switch
{
'\u00A0' => ' ',
'ł' => 'l',
_ => c
});
break;
}
}
return sb.ToString().Normalize(NormalizationForm.FormC);
}
测试代码
static void Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
string s = "Großer Kurfürst, Błyskawica";
string text = string.Concat(Enumerable.Repeat(s, 1000000));
Console.WriteLine(s);
Console.WriteLine(NormalizeUnicode(s));
Stopwatch sw = new Stopwatch();
sw.Start();
string n = NormalizeUnicode(text);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Restart();
string m = NormalizeUnicodeNew(text);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Console.WriteLine(m == n);
Console.ReadKey();
}
发布版本的控制台输出
Großer Kurfürst, Błyskawica
grosser kurfurst, blyskawica
780
562
True
速度的提升真的很明显~15%,这已经是胜利了。我粗略地测量了时间,我还没有运行基准测试。
我想知道你的意见和建议,我还能在哪里调整。有一个想法可以使用string.Create,但它崩溃了,因为我不知道过滤后输出字符串会持续多长时间。
我做出了这个决定。
StringBuilder当然很快,但Span结果更快。与问题中的优化解决方案相比,性能提升了约 20%。在优化过程中,我发现
c switch { }变慢,例如sb.Append(c switch { 'a' => 'b', 'c' => "de" }).if else if- 比switch (...) { }两个分支更快。string.Concat(IEnumerable<char>)比 慢得多new string(IEnumerable<char>.ToArray()),但它并没有派上用场。:)