У меня есть требование , которое является относительно неясным, но он чувствует , как это должно быть возможно с помощью BCL.
Для контекста я анализирую строку даты / времени в Noda Time . Я поддерживаю логический курсор для моей позиции во входной строке. Таким образом, хотя полная строка может быть «3 января 2013 года», логический курсор может быть на «J».
Теперь мне нужно проанализировать название месяца, сравнив его со всеми известными названиями месяцев для культуры:
- С учетом культуры
- Без учета регистра
- Просто от точки курсора (не позже; я хочу видеть, "смотрит ли курсор" на название месяца кандидата)
- Быстро
- ... а потом мне нужно знать, сколько символов было использовано
Текущий код , чтобы сделать это , как правило работает, используя CompareInfo.Compare
. Фактически это примерно так (только для соответствующей части - в реальной вещи больше кода, но он не имеет отношения к совпадению):
internal bool MatchCaseInsensitive(string candidate, CompareInfo compareInfo)
{
return compareInfo.Compare(text, position, candidate.Length,
candidate, 0, candidate.Length,
CompareOptions.IgnoreCase) == 0;
}
Однако это зависит от того, что кандидат и сравниваемая область имеют одинаковую длину. Чистовая большую часть времени, но не хорошо в некоторых особых случаях. Допустим, у нас есть что-то вроде:
// U+00E9 is a single code point for e-acute
var text = "x b\u00e9d y";
int position = 2;
// e followed by U+0301 still means e-acute, but from two code points
var candidate = "be\u0301d";
Теперь мое сравнение не удастся. Я мог бы использовать IsPrefix
:
if (compareInfo.IsPrefix(text.Substring(position), candidate,
CompareOptions.IgnoreCase))
но:
- Для этого мне нужно создать подстроку, чего я бы предпочел избежать. (Я рассматриваю Noda Time как эффективную системную библиотеку; производительность анализа может быть важна для некоторых клиентов.)
- Он не говорит мне, как далеко продвинуть курсор после этого
На самом деле, я сильно подозреваю, что это будет происходить не очень часто ... но мне бы очень хотелось поступить правильно. Я также очень хотел бы иметь возможность делать это, не становясь экспертом по Unicode и не внедряя его самостоятельно :)
(Поднят как ошибка 210 в Noda Time, на случай, если кто-то захочет следовать какому-либо окончательному выводу.)
Мне нравится идея нормализации. Мне нужно подробно проверить это на предмет а) правильности и б) производительности. Предполагая, что я смогу заставить его работать правильно, я все еще не уверен, стоит ли вообще его менять - это такие вещи, которые, вероятно, никогда не появятся в реальной жизни, но могут повредить производительности всех моих пользователей: (
Я также проверил BCL, который, похоже, тоже не справляется с этим должным образом. Образец кода:
using System;
using System.Globalization;
class Test
{
static void Main()
{
var culture = (CultureInfo) CultureInfo.InvariantCulture.Clone();
var months = culture.DateTimeFormat.AbbreviatedMonthNames;
months[10] = "be\u0301d";
culture.DateTimeFormat.AbbreviatedMonthNames = months;
var text = "25 b\u00e9d 2013";
var pattern = "dd MMM yyyy";
DateTime result;
if (DateTime.TryParseExact(text, pattern, culture,
DateTimeStyles.None, out result))
{
Console.WriteLine("Parsed! Result={0}", result);
}
else
{
Console.WriteLine("Didn't parse");
}
}
}
Если изменить название месяца на просто "bed" с текстовым значением "bEd", синтаксический анализ будет выполнен нормально.
Хорошо, еще несколько точек данных:
Стоимость использования
Substring
иIsPrefix
значительна, но не ужасна. В примере «Friday April 12 2013 20:28:42» на моем портативном компьютере для разработки он изменяет количество операций синтаксического анализа, которые я могу выполнить за секунду, с примерно 460K до примерно 400K. Я бы предпочел по возможности избежать этого замедления, но это не так уж плохо.Нормализация менее осуществима, чем я думал, потому что она недоступна в переносимых библиотеках классов. Я потенциально мог бы использовать его только для сборок без PCL, что позволяет сборкам PCL быть немного менее правильными. Тестирование на normalization (
string.IsNormalized
) снижает производительность примерно до 445 тыс. Вызовов в секунду, с чем я могу жить. Я до сих пор не уверен, что он делает все, что мне нужно - например, название месяца, содержащее "ß", должно соответствовать "ss" во многих культурах, я считаю ... и нормализация этого не делает.
text
не слишком длинный, вы можете сделать этоif (compareInfo.IndexOf(text, candidate, position, options) == position)
. msdn.microsoft.com/en-us/library/ms143031.aspx Но еслиtext
это очень долго, это потратит много времени на поиск за пределами того места, где это необходимо.String
класса вообще в данном случае и использоватьChar[]
непосредственно. Вы в конечном итоге напишете больше кода, но это то, что происходит, когда вам нужна высокая производительность ... или, может быть, вам следует программировать на C ++ / CLI ;-)Ответы:
Сначала я рассмотрю проблему многих <-> one / many casemappings отдельно от обработки различных форм нормализации.
Например:
Соответствует,
heisse
но затем слишком сильно перемещает курсор 1. И:Соответствует,
heiße
но затем перемещает курсор 1 меньше.Это будет применяться к любому персонажу, у которого нет простого взаимно-однозначного сопоставления.
Вам нужно будет знать длину фактически совпавшей подстроки. Но…
Compare
иIndexOf
т.д. выбросьте эту информацию. Это может быть возможным с регулярными выражениями , но реализация не делает полный случай складывания и поэтому не соответствует ,ß
чтобыss/SS
в режиме без учета регистра , даже если.Compare
и.IndexOf
делать. И, вероятно, в любом случае создание новых регулярных выражений для каждого кандидата будет дорогостоящим.Самое простое решение - просто хранить строки в свернутой форме и выполнять двоичные сравнения с кандидатами в свернутом регистре. Затем вы можете правильно перемещать курсор,
.Length
поскольку он предназначен для внутреннего представления. Вы также получаете большую часть потерянной производительности из-за того, что вам не нужно его использоватьCompareOptions.IgnoreCase
.К сожалению, нет встроенной функции сворачивания кейсов, и сворачивание кейсов бедняги тоже не работает, потому что нет полного отображения кейсов -
ToUpper
метод не превращаетсяß
вSS
.Например, это работает в Java (и даже в Javascript), учитывая строку, которая находится в нормальной форме C:
Интересно отметить, что сравнение игнорирования регистра в Java не выполняет полного сворачивания регистра, как в C #
CompareOptions.IgnoreCase
. Таким образом, они противоположны в этом отношении: Java выполняет полное отображение регистра, но простое сворачивание регистра - C # делает простое отображение регистра, но полное сворачивание регистра.Поэтому вполне вероятно, что вам понадобится сторонняя библиотека, чтобы свернуть строки перед их использованием.
Прежде чем что-либо делать, вы должны убедиться, что ваши строки имеют нормальную форму C. Вы можете использовать эту предварительную быструю проверку, оптимизированную для латинского алфавита:
Это дает ложные срабатывания, но не ложноотрицательные, я не ожидаю, что это вообще замедлит 460 тыс. Синтаксических анализов / с при использовании символов латинского алфавита, даже если это необходимо выполнять для каждой строки. При ложном срабатывании вы могли бы
IsNormalized
получить истинно отрицательный / положительный результат и только после этого при необходимости нормализовать.Итак, в заключение, обработка должна сначала обеспечить нормальную форму C, а затем регистр. Выполняйте двоичные сравнения с обработанными строками и перемещайте курсор по мере его перемещения.
источник
æ
равноae
иffi
равноffi
. C-нормализация вообще не обрабатывает лигатуры, поскольку позволяет только сопоставления совместимости (которые обычно ограничиваются объединением символов).ffi
, но пропускает другие, напримерæ
. Проблема усугубляется несоответствием между культурами -æ
равноae
en-US, но не da-DK, как описано в документации MSDN для строк . Таким образом, нормализация (к любой форме) и отображение случаев не являются достаточным решением этой проблемы.Посмотрите, соответствует ли это требованиям ..:
compareInfo.Compare
выполняет только один раз,source
начатый сprefix
; если нет, тоIsPrefix
возвращается-1
; в противном случае длина символов, используемых вsource
.Тем не менее, я понятия не имею , кроме прироста
length2
по1
со следующим случаем:обновление :
Я попытался немного улучшить производительность, но не доказано, есть ли ошибка в следующем коде:
Я тестировал конкретный случай, и сравнение снизилось примерно до 3.
источник
IndexOf
операция должна просматривать всю строку с начальной позиции, что может снизить производительность, если входная строка длинная.На самом деле это возможно без нормализации и без использования
IsPrefix
.Нам нужно сравнить такое же количество текстовых элементов, а не такое же количество символов, но все же вернуть количество совпадающих символов.
Я создал копию
MatchCaseInsensitive
метода из ValueCursor.cs в Noda Time и немного изменил его, чтобы его можно было использовать в статическом контексте:(Только для справки, это код, который, как вы знаете, некорректно сравнивать)
Следующий вариант этого метода использует StringInfo.GetNextTextElement, предоставляемый платформой. Идея состоит в том, чтобы сравнить текстовый элемент с текстовым элементом, чтобы найти совпадение и, если оно найдено, вернуть фактическое количество совпадающих символов в исходной строке:
Этот метод отлично работает, по крайней мере, в соответствии с моими тестовыми примерами (которые в основном просто проверяют пару вариантов предоставленных вами строк:
"b\u00e9d"
и"be\u0301d"
).Однако метод GetNextTextElement создает подстроку для каждого текстового элемента, поэтому эта реализация требует большого количества сравнений подстрок, что повлияет на производительность.
Итак, я создал другой вариант, который не использует GetNextTextElement, а вместо этого пропускает объединяющие символы Unicode, чтобы найти фактическую длину совпадения в символах:
В этом методе используются два следующих помощника:
Я не проводил тестирования производительности, поэтому не знаю, действительно ли более быстрый метод быстрее. Я также не проводил расширенного тестирования.
Но это должно ответить на ваш вопрос о том, как выполнить сопоставление подстрок с учетом культурных особенностей для строк, которые могут включать символы объединения Unicode.
Я использовал следующие тестовые примеры:
Значения кортежа:
Выполнение этих тестов для трех методов дает следующий результат:
Последние два теста проверяют случай, когда исходная строка короче строки соответствия. В этом случае будет успешно использован и оригинальный метод (время Noda).
источник