Перечисление строки по графеме вместо символа - PullRequest
7 голосов
/ 13 января 2010

Строки обычно перечисляются по символам. Но, особенно при работе с Unicode и неанглийскими языками, иногда мне нужно перечислить строку по графеме. То есть сочетание знаков и диакритических знаков должно храниться с базовым символом, который они изменяют. Каков наилучший способ сделать это в .Net?

Вариант использования: Подсчитать различные фонетические звуки в серии IPA слов.

  1. Упрощенное определение: Между графемой и звуком существует взаимно-однозначное отношение.
  2. Реалистичное определение: Специальные «буквенные» символы также должны быть включены в базовый символ (например, pʰ), а некоторые звуки могут быть представлены двумя символами, соединенными перемычкой (k͡p) .

Ответы [ 2 ]

6 голосов
/ 13 января 2010

Упрощенный сценарий

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.

Возможно, мы могли бы также создать наш собственный IEnumerator , используя CharUnicodeInfo.GetUnicodeCategory, чтобы восстановить нашу производительность, но мне кажется, что это слишком большая работа и дополнительный код для обслуживания. (Кто-нибудь еще хочет пойти?) Для этого созданы регулярные выражения.

1 голос
/ 13 января 2010

Я не уверен, что это именно то, что вы ищете, но разве ваш вопрос не связан с нормализацией Unicode?

Когда строка нормализована к форме Юникод C (которая является формой по умолчанию), диакритические знаки и изменяемые ими символы объединяются, поэтому, если вы перечислите символы, вы получите базовые и модифицирующие символы вместе.

Когда он нормализован к форме D, базовые символы и символы-модификаторы разделяются и возвращаются в перечислении отдельно.

Подробнее см. String.Normalize

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...