Maiúsculas vs minúsculas

86

Ao fazer comparações que não diferenciam maiúsculas de minúsculas, é mais eficiente converter a string em maiúsculas ou minúsculas? Isso importa mesmo?

É sugerido neste post do SO que o C # é mais eficiente com o ToUpper porque "a Microsoft o otimizou dessa maneira." Mas eu também li este argumento de que a conversão de ToLower vs. ToUpper depende do que suas strings contêm mais, e que normalmente as strings contêm mais caracteres minúsculos, o que torna ToLower mais eficiente.

Em particular, gostaria de saber:

  • Existe uma maneira de otimizar ToUpper ou ToLower de forma que um seja mais rápido que o outro?
  • É mais rápido fazer uma comparação sem distinção entre maiúsculas e minúsculas e por quê?
  • Existe algum ambiente de programação (por exemplo, C, C #, Python, qualquer que seja) onde um caso é claramente melhor do que o outro e por quê?
Parappa
fonte

Respostas:

90

Converter para maiúsculas ou minúsculas para fazer comparações que não diferenciam maiúsculas de minúsculas é incorreto devido a características "interessantes" de algumas culturas, particularmente a Turquia. Em vez disso, use um StringComparer com as opções apropriadas.

O MSDN tem algumas ótimas diretrizes sobre o manuseio de strings. Você também pode querer verificar se seu código passa no teste de Turquia .

EDIT: Observe o comentário de Neil sobre comparações ordinais que não diferenciam maiúsculas de minúsculas. Todo este reino é bastante obscuro :(

Jon Skeet
fonte
15
Sim, StringComparer é ótimo, mas a pergunta não foi respondida ... Em situações onde você não pode usar StringComparer, como uma instrução swtich em uma string; devo ToUpper ou ToLower no switch?
joshperry
7
Use um StringComparer e "if" / "else" em vez de usar ToUpper ou ToLower.
Jon Skeet
5
John, eu sei que converter para minúsculas está incorreto, mas não tinha ouvido dizer que converter para maiúsculas é incorreto. Você pode dar um exemplo ou uma referência? O artigo do MSDN ao qual você criou um link diz o seguinte: "As comparações feitas usando OrdinalIgnoreCase são comportamentais a composição de duas chamadas: chamar ToUpperInvariant em ambos os argumentos de string e fazer uma comparação Ordinal." Na seção intitulada "Operações de String Ordinal", ele reafirma isso em código.
Neil
2
@ Neil: Interessante, eu não tinha visto essa parte. Para uma comparação ordinal que não diferencia maiúsculas de minúsculas, acho que isso é justo. Afinal, tem que escolher algo . Para comparações sem distinção entre maiúsculas e minúsculas culturalmente sensíveis, acho que ainda haveria espaço para alguns comportamentos estranhos. Vou apontar o seu comentário na resposta ...
Jon Skeet
4
@Triynko: Acho que é importante se concentrar principalmente na correção, com o ponto de que obter a resposta errada rápido geralmente não é melhor (e às vezes é pior) do que obter a resposta errada lentamente.
Jon Skeet de
25

Da Microsoft no MSDN:

Práticas recomendadas para usar strings no .NET Framework

Recomendações para uso de string

Por quê? Da Microsoft :

Normalizar strings para maiúsculas

Há um pequeno grupo de caracteres que, quando convertidos para letras minúsculas, não podem fazer um percurso completo.

Qual é o exemplo de um personagem que não pode fazer uma viagem de ida e volta?

  • Início : símbolo grego Rho (U + 03f1) ϱ
  • Maiúsculas: Rho grego maiúsculo (U + 03a1) Ρ
  • Minúsculas: Rho grego pequeno (U + 03c1) ρ

ϱ, Ρ , ρ

.NET Fiddle

Original: ϱ
ToUpper: Ρ
ToLower: ρ

É por isso que, se você quiser fazer comparações sem distinção entre maiúsculas e minúsculas, converta as strings em maiúsculas, e não em minúsculas.

Portanto, se você tiver que escolher um, escolha Maiúsculas .

Ian Boyd
fonte
3
De volta à resposta à pergunta original: Existem idiomas que conhecem mais de uma variante em minúsculas para uma variante em maiúsculas. A menos que você conheça as regras de quando usar qual representação (outro exemplo em grego: letra sigma minúscula, você usa σ no início ou no meio da palavra, ς no final das palavras (ver en.wikipedia.org/wiki/Sigma ), você não pode converter com segurança de volta para a variante em minúsculas.
Aconcágua
Na verdade, e quanto ao alemão 'ß', se você chamá- ToUpper()lo se tornará 'SS' em muitos sistemas. Portanto, também não é possível fazer viagens de ida e volta.
Sebastian
se a Microsoft otimizou o código para realizar comparações em maiúsculas é porque o código ASCII para letras maiúsculas apenas dois dígitos 65 - 90 enquanto o código ASCII Letras minúsculas 97 -122 que contém 3 dígitos (precisa de mais processamento)
Medo Medo
Deve-se notar que ambos "ϱ" e "ς" retornam ToUpperInvariant(), então ainda seria bom ver exemplos reais de porque maiúsculas é melhor do que minúsculas
max630
Esta resposta não parece ser relevante. De acordo com o link da Microsoft, isso só importa ao alterar a localidade de uma string: "Fazer uma viagem de ida e volta significa converter os caracteres de uma localidade em outra localidade que representa dados de caractere de forma diferente e, em seguida, recuperar com precisão os caracteres originais do caracteres convertidos. " Mas a questão não envolve a conversão para um local diferente.
Toolmaker Steve
18

De acordo com o MSDN , é mais eficiente passar as strings e dizer à comparação para ignorar maiúsculas e minúsculas:

String.Compare (strA, strB, StringComparison.OrdinalIgnoreCase) é equivalente a ( mas mais rápido que ) chamar

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

Essas comparações ainda são muito rápidas.

Claro, se você estiver comparando uma corda repetidamente, isso pode não funcionar.

Rob Walker
fonte
11

Com base em strings que tendem a ter mais entradas em minúsculas, ToLower deveria teoricamente ser mais rápido (muitas comparações, mas poucas atribuições).

Em C, ou ao usar elementos acessíveis individualmente de cada string (como strings C ou o tipo de string STL em C ++), é na verdade uma comparação de bytes - então comparar UPPERnão é diferente de lower.

Se você fosse sorrateiro e carregasse suas strings em longarrays, obteria uma comparação muito rápida de toda a string porque ela poderia comparar 4 bytes por vez. No entanto, o tempo de carregamento pode fazer com que não valha a pena.

Por que você precisa saber o que é mais rápido? A menos que você esteja fazendo um monte de comparações métricas, uma execução alguns ciclos mais rápido é irrelevante para a velocidade de execução geral e soa como otimização prematura :)

Warren
fonte
11
Para responder à pergunta por que preciso saber o que é mais rápido: não preciso saber, apenas quero saber. :) É simplesmente o caso de ver alguém fazer uma afirmação (como "comparar strings em maiúsculas é mais rápido!") E querer saber se é realmente verdade e / ou porque fez essa afirmação.
Parappa
1
isso faz sentido - estou eternamente curioso por coisas como esta também :)
warren
1
Com strings C, para converter se tem arrays de longs de modo que as strings sejam iguais se os arrays forem iguais, você precisa percorrer s e t até encontrar o '\0'caractere de terminação (ou então você pode comparar o lixo após o final das strings, que pode ser um acesso ilegal à memória que invoca um comportamento indefinido). Mas então por que não fazer as comparações enquanto examina os personagens um por um? Com strings C ++, você provavelmente pode obter o comprimento e .c_str(), converter em ae long *comparar um prefixo de comprimento .size() - .size()%(sizeof long). Parece um pouco suspeito para mim, tho.
Jonas Kölker
@ JonasKölker - carregar a string em um array de longs apenas para fins de comparação seria tolice. Mas se você está fazendo "muito" - posso ver um possível argumento para que seja feito.
Warren
5

A Microsoft otimizou ToUpperInvariant(), não ToUpper(). A diferença é que invariante é mais amigável à cultura. Se você precisar fazer comparações que não diferenciam maiúsculas de minúsculas em strings que podem variar na cultura, use Invariante, caso contrário, o desempenho da conversão invariável não deve importar.

Não posso dizer se ToUpper () ou ToLower () é mais rápido. Nunca tentei, pois nunca tive uma situação em que o desempenho importasse tanto.

Dan Herbert
fonte
se a Microsoft otimizou o código para realizar comparações em maiúsculas, é porque o código ASCII para letras maiúsculas apenas dois dígitos 65 - 90 enquanto o código ASCII Letras minúsculas 97 -122 que contém 3 dígitos (precisa de mais processamento)?
Medo Medo de
4
@Medo Não me lembro dos motivos exatos da otimização, mas 2 contra 3 dígitos quase certamente não é o motivo, já que todas as letras são armazenadas como números binários, portanto, os dígitos decimais realmente não têm significado com base na maneira como são armazenados.
Dan Herbert
3

Se você estiver fazendo comparação de strings em C #, é significativamente mais rápido usar .Equals () em vez de converter ambas as strings em maiúsculas ou minúsculas. Outra grande vantagem para usar .Equals () é que mais memória não é alocada para as 2 novas strings maiúsculas / minúsculas.

Jon Tackabury
fonte
4
E como um bônus, se você escolher as opções certas, os resultados corretos serão realmente obtidos :)
Jon Skeet
0

Realmente não deveria importar. Com caracteres ASCII, definitivamente não importa - são apenas algumas comparações e um pouco de mudança para qualquer direção. Unicode pode ser um pouco mais complicado, já que existem alguns caracteres que mudam de caixa de maneira estranha, mas realmente não deve haver nenhuma diferença a menos que seu texto esteja cheio desses caracteres especiais.

Adam Rosenfield
fonte
0

Fazendo isso da maneira certa, deve haver uma pequena e insignificante vantagem de velocidade se você converter para minúsculas, mas isso é, como muitos sugeriram, dependente da cultura e não é herdado na função, mas nas strings que você converte (muitas letras minúsculas significa poucas atribuições na memória) - converter para maiúsculas é mais rápido se você tiver uma string com muitas letras maiúsculas.

Mais claro
fonte
0

Eu queria alguns dados reais sobre isso, então puxei a lista completa de dois caracteres de byte que falham com ToLowerou ToUpper. Em seguida, executei este teste abaixo:

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);
   }
}

Resultado abaixo. Observe que também testei com as Invariantversões e o resultado foi exatamente o mesmo. Curiosamente, um dos pares falha com ambos. Mas com base nisso, ToUpper é a melhor opção .

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
Steven Penny
fonte