Верхний и нижний регистр

86

При сравнении без учета регистра более эффективно преобразовать строку в верхний или нижний регистр? Это вообще имеет значение?

В этом сообщении SO предлагается, что C # более эффективен с ToUpper, потому что «Microsoft оптимизировала его таким образом». Но я также читал этот аргумент, что преобразование ToLower и ToUpper зависит от того, что ваши строки содержат больше, и что обычно строки содержат больше символов нижнего регистра, что делает ToLower более эффективным.

В частности, хотелось бы знать:

  • Есть ли способ оптимизировать ToUpper или ToLower таким образом, чтобы один был быстрее другого?
  • Быстрее ли проводить сравнение строк в верхнем или нижнем регистре без учета регистра и почему?
  • Существуют ли какие-либо среды программирования (например, C, C #, Python и т. Д.), В которых один случай явно лучше другого, и почему?
Параппа
источник

Ответы:

90

Преобразование в верхний или нижний регистр для сравнений без учета регистра неверно из-за "интересных" особенностей некоторых культур, особенно Турции. Вместо этого используйте StringComparer с соответствующими параметрами.

В MSDN есть несколько отличных рекомендаций по обработке строк. Вы также можете проверить, проходит ли ваш код тест для Турции .

РЕДАКТИРОВАТЬ: обратите внимание на комментарий Нила относительно порядковых сравнений без учета регистра. Весь этот мир довольно темный :(

Джон Скит
источник
15
Да StringComparer великолепен, но на вопрос не ответили ... В ситуациях, когда вы не можете использовать StringComparer, например, выражение swtich для строки; мне нужно ToUpper или ToLower в коммутаторе?
Джошперри
7
Используйте StringComparer и «if» / «else» вместо ToUpper или ToLower.
Джон Скит,
5
Джон, я знаю, что преобразование в нижний регистр неверно, но я не слышал, что преобразование в верхний регистр неверно. Можете предложить пример или ссылку? В статье MSDN, на которую вы ссылаетесь, говорится следующее: «Сравнения, выполненные с использованием OrdinalIgnoreCase, поведенчески представляют собой композицию двух вызовов: вызов ToUpperInvariant для обоих строковых аргументов и выполнение порядкового сравнения». В разделе «Операции с порядковыми строками» это повторяется в коде.
Нил
2
@ Нил: Интересно, я этого не видел. Думаю, этого достаточно для порядкового сравнения без учета регистра. В конце концов, нужно что-то выбрать . Я думаю, что для сравнений без учета регистра с учетом культурных особенностей все еще будет место для некоторого странного поведения. Укажу на ваш комментарий в ответе ...
Джон Скит
4
@Triynko: Я думаю, что важно сосредоточиться в первую очередь на правильности, с той точки зрения, что быстро получить неправильный ответ обычно не лучше (а иногда и хуже), чем медленно получать неправильный ответ.
Джон Скит,
25

От Microsoft в MSDN:

Рекомендации по использованию строк в .NET Framework

Рекомендации по использованию строк

Почему? От Microsoft :

Привести строки в верхний регистр

Есть небольшая группа символов, которые при преобразовании в нижний регистр не могут пройти туда и обратно.

Каков пример такого персонажа, который не может совершить путешествие туда и обратно?

  • Начало : Греческий символ Ро (U + 03f1) ϱ
  • Прописные буквы : Заглавный греческий Rho (U + 03a1) Ρ
  • Строчные: строчные греческие Rho (U + 03c1) ρ

ϱ, Ρ , ρ

.NET Fiddle

Original: ϱ
ToUpper: Ρ
ToLower: ρ

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

Поэтому, если вам нужно выбрать один, выберите Прописные буквы .

Ян Бойд
источник
3
Вернемся к ответу на исходный вопрос: существуют языки, знающие более одного варианта нижнего регистра для одного варианта верхнего регистра. Если вы не знаете правила того, когда и какое представление использовать (другой пример на греческом: маленькая сигма-буква, вы используете σ в начале или в середине слова, ς в конце слова (см. En.wikipedia.org/wiki/Sigma ), вы не можете безопасно преобразовать обратно в вариант нижнего регистра.
Аконкагуа
Собственно, как насчет немецкой «ß», если ее называть, ToUpper()она во многих системах превратится в «SS». Так что на самом деле это тоже невозможно.
Себастьян
если Microsoft оптимизировала код для выполнения сравнения в верхнем регистре, это потому, что код ASCII для прописных букв только две цифры 65-90, а код ASCII - строчные буквы 97-122, который содержит 3 цифры (требуется дополнительная обработка)
Medo Medo,
Следует отметить, что и «ϱ», и «ς» возвращаются из ToUpperInvariant(), поэтому было бы неплохо увидеть реальные примеры, почему верхний регистр лучше строчного
max630
Этот ответ не кажется актуальным. Согласно ссылке Microsoft, это имеет значение только при изменении языкового стандарта строки: «Сделать круговой обход означает преобразовать символы из одного языкового стандарта в другой языковой стандарт, который по-разному представляет символьные данные, а затем точно извлечь исходные символы из преобразованные символы ". Но вопрос не в переводе на другую локаль.
ToolmakerSteve
18

Согласно MSDN, более эффективно передавать строки и указывать при сравнении игнорировать регистр:

String.Compare (strA, strB, StringComparison.OrdinalIgnoreCase) эквивалентен ( но быстрее ) вызову

String.Compare (ToUpperInvariant (strA), ToUpperInvariant (strB), StringComparison.Ordinal).

Эти сравнения все еще очень быстрые.

Конечно, если вы снова и снова сравниваете одну строку, это может не выполняться.

Роб Уокер
источник
11

Основываясь на том, что строки имеют больше записей в нижнем регистре, ToLower теоретически должен быть быстрее (много сравнений, но мало присваиваний).

В C или при использовании индивидуально доступных элементов каждой строки (таких как строки C или строковый тип STL в C ++) на самом деле это сравнение байтов, поэтому сравнение UPPERничем не отличается от lower.

Если бы вы были хитрыми и longвместо этого загрузили свои строки в массивы, вы бы получили очень быстрое сравнение всей строки, потому что оно могло бы сравнивать 4 байта за раз. Однако время загрузки может сделать это нецелесообразным.

Зачем нужно знать, что быстрее? Если вы не выполняете метрическую контрольную нагрузку сравнений, одно, выполняемое на пару циклов быстрее, не имеет отношения к скорости общего выполнения и звучит как преждевременная оптимизация :)

Уоррен
источник
11
Чтобы ответить на вопрос, почему мне нужно знать, что быстрее: мне не нужно знать, я просто хочу знать. :) Это просто случай, когда кто-то делает заявление (например, «сравнение строк в верхнем регистре быстрее!») И хочет знать, правда ли это на самом деле и / или почему они сделали это утверждение.
Parappa
1
это имеет смысл - мне тоже вечно любопытно такие вещи :)
Уоррен
1
С строками C для преобразования sи tв массивы long, такие, что строки равны, если массивы равны, вам нужно пройти вниз s и t, пока не найдете завершающий '\0'символ (или вы можете сравнить мусор за концом строк, что может быть незаконным доступом к памяти, вызывающим неопределенное поведение). Но тогда почему бы просто не провести сравнения, проходя по персонажам одного за другим? С помощью строк C ++ вы, вероятно, можете получить длину и .c_str(), привести к long *и сравнить префикс длины .size() - .size()%(sizeof long). Мне кажется, это немного подозрительно.
Йонас Кёлькер
@ JonasKölker - загружать строку в массив longs только для сравнения было бы глупо. Но если вы делаете это «много» - я мог бы увидеть возможный аргумент в пользу того, чтобы это было сделано.
Уоррен
5

Microsoft оптимизировала ToUpperInvariant(), нет ToUpper(). Разница в том, что инвариант более дружелюбен к культуре. Если вам нужно выполнить сравнение без учета регистра для строк, которые могут отличаться по культуре, используйте Invariant, иначе производительность инвариантного преобразования не имеет значения.

Я не могу сказать, что быстрее - ToUpper () или ToLower (). Я никогда не пробовал это, поскольку у меня никогда не было ситуации, когда производительность имела такое большое значение.

Дэн Герберт
источник
если Microsoft оптимизировала код для выполнения сравнения в верхнем регистре, это потому, что в коде ASCII для прописных букв только две цифры 65–90, а в коде ASCII - строчные буквы 97–122, который содержит 3 цифры (требуется дополнительная обработка)?
Медо Медо
4
@Medo Я не помню точных причин для оптимизации, но две цифры против трех почти наверняка не причина, поскольку все буквы хранятся как двоичные числа, поэтому десятичные цифры на самом деле не имеют значения в зависимости от того, как они хранятся.
Дэн Герберт
3

Если вы выполняете сравнение строк в C #, значительно быстрее использовать .Equals () вместо преобразования обеих строк в верхний или нижний регистр. Еще один большой плюс использования .Equals () заключается в том, что больше памяти не выделяется для двух новых строк верхнего / нижнего регистра.

Джон Тэкабери
источник
4
И в качестве бонуса, если вы выберете правильные варианты, это действительно даст вам правильные результаты :)
Джон Скит,
0

Это действительно не должно иметь никакого значения. С символами ASCII это определенно не имеет значения - это всего лишь несколько сравнений и небольшое изменение в любом направлении. Unicode может быть немного сложнее, поскольку есть некоторые символы, которые странным образом меняют регистр, но на самом деле не должно быть никакой разницы, если ваш текст не заполнен этими специальными символами.

Адам Розенфилд
источник
0

При правильном выполнении должно быть небольшое, незначительное преимущество в скорости, если вы конвертируете в нижний регистр, но это, как многие намекали, зависит от культуры и наследуется не в функции, а в строках, которые вы конвертируете (много строчных букв означает несколько присвоений памяти) - преобразование в верхний регистр происходит быстрее, если у вас есть строка с большим количеством заглавных букв.

Яснее
источник
0

Мне нужны были фактические данные об этом, поэтому я вытащил полный список двухбайтовых символов, которые не работают с ToLowerили ToUpper. Затем я провел этот тест ниже:

using System;

class Program {
   static void Main() {
      char[][] pairs = {
new[]{'\u00E5','\u212B'},new[]{'\u00C5','\u212B'},new[]{'\u0399','\u1FBE'},
new[]{'\u03B9','\u1FBE'},new[]{'\u03B2','\u03D0'},new[]{'\u03B5','\u03F5'},
new[]{'\u03B8','\u03D1'},new[]{'\u03B8','\u03F4'},new[]{'\u03D1','\u03F4'},
new[]{'\u03B9','\u1FBE'},new[]{'\u0345','\u03B9'},new[]{'\u0345','\u1FBE'},
new[]{'\u03BA','\u03F0'},new[]{'\u00B5','\u03BC'},new[]{'\u03C0','\u03D6'},
new[]{'\u03C1','\u03F1'},new[]{'\u03C2','\u03C3'},new[]{'\u03C6','\u03D5'},
new[]{'\u03C9','\u2126'},new[]{'\u0392','\u03D0'},new[]{'\u0395','\u03F5'},
new[]{'\u03D1','\u03F4'},new[]{'\u0398','\u03D1'},new[]{'\u0398','\u03F4'},
new[]{'\u0345','\u1FBE'},new[]{'\u0345','\u0399'},new[]{'\u0399','\u1FBE'},
new[]{'\u039A','\u03F0'},new[]{'\u00B5','\u039C'},new[]{'\u03A0','\u03D6'},
new[]{'\u03A1','\u03F1'},new[]{'\u03A3','\u03C2'},new[]{'\u03A6','\u03D5'},
new[]{'\u03A9','\u2126'},new[]{'\u0398','\u03F4'},new[]{'\u03B8','\u03F4'},
new[]{'\u03B8','\u03D1'},new[]{'\u0398','\u03D1'},new[]{'\u0432','\u1C80'},
new[]{'\u0434','\u1C81'},new[]{'\u043E','\u1C82'},new[]{'\u0441','\u1C83'},
new[]{'\u0442','\u1C84'},new[]{'\u0442','\u1C85'},new[]{'\u1C84','\u1C85'},
new[]{'\u044A','\u1C86'},new[]{'\u0412','\u1C80'},new[]{'\u0414','\u1C81'},
new[]{'\u041E','\u1C82'},new[]{'\u0421','\u1C83'},new[]{'\u1C84','\u1C85'},
new[]{'\u0422','\u1C84'},new[]{'\u0422','\u1C85'},new[]{'\u042A','\u1C86'},
new[]{'\u0463','\u1C87'},new[]{'\u0462','\u1C87'}
      };
      int upper = 0, lower = 0;
      foreach (char[] pair in pairs) {
         Console.Write(
            "U+{0:X4} U+{1:X4} pass: ",
            Convert.ToInt32(pair[0]),
            Convert.ToInt32(pair[1])
         );
         if (Char.ToUpper(pair[0]) == Char.ToUpper(pair[1])) {
            Console.Write("ToUpper ");
            upper++;
         } else {
            Console.Write("        ");
         }
         if (Char.ToLower(pair[0]) == Char.ToLower(pair[1])) {
            Console.Write("ToLower");
            lower++;
         }
         Console.WriteLine();
      }
      Console.WriteLine("upper pass: {0}, lower pass: {1}", upper, lower);
   }
}

Результат ниже. Обратите внимание, что я также тестировал эти Invariantверсии, и результат был точно таким же. Интересно, что одна из пар не справляется с обоими. Но исходя из этого ToUpper - лучший вариант .

U+00E5 U+212B pass:         ToLower
U+00C5 U+212B pass:         ToLower
U+0399 U+1FBE pass: ToUpper
U+03B9 U+1FBE pass: ToUpper
U+03B2 U+03D0 pass: ToUpper
U+03B5 U+03F5 pass: ToUpper
U+03B8 U+03D1 pass: ToUpper
U+03B8 U+03F4 pass:         ToLower
U+03D1 U+03F4 pass:
U+03B9 U+1FBE pass: ToUpper
U+0345 U+03B9 pass: ToUpper
U+0345 U+1FBE pass: ToUpper
U+03BA U+03F0 pass: ToUpper
U+00B5 U+03BC pass: ToUpper
U+03C0 U+03D6 pass: ToUpper
U+03C1 U+03F1 pass: ToUpper
U+03C2 U+03C3 pass: ToUpper
U+03C6 U+03D5 pass: ToUpper
U+03C9 U+2126 pass:         ToLower
U+0392 U+03D0 pass: ToUpper
U+0395 U+03F5 pass: ToUpper
U+03D1 U+03F4 pass:
U+0398 U+03D1 pass: ToUpper
U+0398 U+03F4 pass:         ToLower
U+0345 U+1FBE pass: ToUpper
U+0345 U+0399 pass: ToUpper
U+0399 U+1FBE pass: ToUpper
U+039A U+03F0 pass: ToUpper
U+00B5 U+039C pass: ToUpper
U+03A0 U+03D6 pass: ToUpper
U+03A1 U+03F1 pass: ToUpper
U+03A3 U+03C2 pass: ToUpper
U+03A6 U+03D5 pass: ToUpper
U+03A9 U+2126 pass:         ToLower
U+0398 U+03F4 pass:         ToLower
U+03B8 U+03F4 pass:         ToLower
U+03B8 U+03D1 pass: ToUpper
U+0398 U+03D1 pass: ToUpper
U+0432 U+1C80 pass: ToUpper
U+0434 U+1C81 pass: ToUpper
U+043E U+1C82 pass: ToUpper
U+0441 U+1C83 pass: ToUpper
U+0442 U+1C84 pass: ToUpper
U+0442 U+1C85 pass: ToUpper
U+1C84 U+1C85 pass: ToUpper
U+044A U+1C86 pass: ToUpper
U+0412 U+1C80 pass: ToUpper
U+0414 U+1C81 pass: ToUpper
U+041E U+1C82 pass: ToUpper
U+0421 U+1C83 pass: ToUpper
U+1C84 U+1C85 pass: ToUpper
U+0422 U+1C84 pass: ToUpper
U+0422 U+1C85 pass: ToUpper
U+042A U+1C86 pass: ToUpper
U+0463 U+1C87 pass: ToUpper
U+0462 U+1C87 pass: ToUpper
upper pass: 46, lower pass: 8
Стивен Пенни
источник