字符串通常按字符枚举。但是,特别是在使用Unicode和非英语语言时,有时我需要按字形枚举字符串。也就是说,应将标记和变音符号结合起来使用其修改的基本字符。 .NET中执行此操作的最佳方法是什么?

用例:计算一系列IPA单词中的不同语音。


简化定义:字素与声音之间存在一对一的关系。
现实的定义:特殊的“类似字母”字符也应包括在基本字符(例如pʰ)中,某些声音可能由用拉杆(k͡p)连接的两个符号表示。

最佳答案

简化方案

TextElementEnumerator是非常有用和高效的:

private static List<SoundCount> CountSounds(IEnumerable<string> words)
{
    Dictionary<string, SoundCount> soundCounts = new Dictionary<string, SoundCount>();

    foreach (var word in words)
    {
        TextElementEnumerator graphemeEnumerator = StringInfo.GetTextElementEnumerator(word);
        while (graphemeEnumerator.MoveNext())
        {
            string grapheme = graphemeEnumerator.GetTextElement();

            SoundCount count;
            if (!soundCounts.TryGetValue(grapheme, out count))
            {
                count = new SoundCount() { Sound = grapheme };
                soundCounts.Add(grapheme, count);
            }
            count.Count++;
        }
    }

    return new List<SoundCount>(soundCounts.Values);
}


您也可以使用正则表达式来做到这一点:(从文档中,TextElementEnumerator处理以下情况下的某些情况,尤其是补充字符,但这些情况很少见,无论如何我的应用程序都不需要)。

private static List<SoundCount> CountSoundsRegex(IEnumerable<string> words)
{
    var soundCounts = new Dictionary<string, SoundCount>();
    var graphemeExpression = new Regex(@"\P{M}\p{M}*");

    foreach (var word in words)
    {
        Match graphemeMatch = graphemeExpression.Match(word);
        while (graphemeMatch.Success)
        {
            string grapheme = graphemeMatch.Value;

            SoundCount count;
            if (!soundCounts.TryGetValue(grapheme, out count))
            {
                count = new SoundCount() { Sound = grapheme };
                soundCounts.Add(grapheme, count);
            }
            count.Count++;

            graphemeMatch = graphemeMatch.NextMatch();
        }
    }

    return new List<SoundCount>(soundCounts.Values);
}


性能:在测试中,我发现TextElementEnumerator的速度约为正则表达式的4倍。

现实情况

不幸的是,没有办法“调整” TextElementEnumerator的枚举方式,因此在实际情况下该类将毫无用处。

一种解决方案是调整我们的正则表达式:

[\P{M}\P{Lm}]      # Match a character that is NOT a character intended to be combined with another character or a special character that is used like a letter
(?:                # Start a group for the combining characters:
  (?:                # Start a group for tied characters:
    [\u035C\u0361]      # Match an under- or over- tie bar...
    \P{M}\p{M}*         # ...followed by another grapheme (in the simplified sense)
  )                  # (End the tied characters group)
  |\p{M}             # OR a character intended to be combined with another character
  |\p{Lm}            # OR a special character that is used like a letter
)*                 # Match the combining characters group zero or more times.


我们可能还可以使用CharUnicodeInfo.GetUnicodeCategory创建自己的IEnumerator 来恢复性能,但是这对我来说似乎太多了,需要额外的代码来维护。 (还有其他人想去吗?)为此制作了正则表达式。

10-08 08:56
查看更多