Ignorando letras acentuadas na comparação de cadeias

141

Preciso comparar duas strings em C # e tratar as letras acentuadas da mesma forma que as letras não acentuadas. Por exemplo:

string s1 = "hello";
string s2 = "héllo";

s1.Equals(s2, StringComparison.InvariantCultureIgnoreCase);
s1.Equals(s2, StringComparison.OrdinalIgnoreCase);

Essas duas strings precisam ser as mesmas (no que diz respeito ao meu aplicativo), mas ambas as instruções são avaliadas como falsas. Existe uma maneira em c # para fazer isso?

Jon Tackabury
fonte

Respostas:

251

EDIT 20-01-2012: Oh garoto! A solução era muito mais simples e está na estrutura quase sempre. Como apontado por knightpfhor :

string.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace);

Aqui está uma função que retira diacríticos de uma sequência:

static string RemoveDiacritics(string text)
{
  string formD = text.Normalize(NormalizationForm.FormD);
  StringBuilder sb = new StringBuilder();

  foreach (char ch in formD)
  {
    UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(ch);
    if (uc != UnicodeCategory.NonSpacingMark)
    {
      sb.Append(ch);
    }
  }

  return sb.ToString().Normalize(NormalizationForm.FormC);
}

Mais detalhes no blog de MichKap ( RIP ... ).

O princípio é que ele transforma 'é' em 2 caracteres sucessivos 'e', ​​agudos. Em seguida, itera pelos caracteres e ignora os sinais diacríticos.

"héllo" se torna "he <acute> llo", que por sua vez se torna "olá".

Debug.Assert("hello"==RemoveDiacritics("héllo"));

Nota: Aqui está uma versão mais compacta do .NET4 + da mesma função:

static string RemoveDiacritics(string text)
{
  return string.Concat( 
      text.Normalize(NormalizationForm.FormD)
      .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch)!=
                                    UnicodeCategory.NonSpacingMark)
    ).Normalize(NormalizationForm.FormC);
}
Serge Wautier
fonte
1
Como fazê-lo no núcleo .net, uma vez que não possui string.Normalize?
André Soares
Obrigado por isso, eu gostaria de poder votar mais de uma vez! No entanto, ele não processa todas as letras acentuadas, por exemplo ð, ħ e ø não são convertidos em o, heo respectivamente. Existe alguma maneira de lidar com isso também?
Avrohom Yisroel
@AvrohomYisroel, o "ð" é um "Latin Small Letter Eth", que é uma letra separada, não um "o-com-sotaque" ou "d-com-sotaque". Os outros são "Letra minúscula latina H com traço" e "Letra minúscula latina O com traço" que também podem ser considerados letras separadas
Hans Keing em
135

Se você não precisa converter a string e deseja apenas verificar a igualdade, pode usar

string s1 = "hello";
string s2 = "héllo";

if (String.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace) == 0)
{
    // both strings are equal
}

ou se você quiser que a comparação também não diferencie maiúsculas de minúsculas

string s1 = "HEllO";
string s2 = "héLLo";

if (String.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase) == 0)
{
    // both strings are equal
}
knightpfhor
fonte
Se alguém mais estiver curioso sobre esta opção IgnoreNonSpace, convém ler esta discussão. pcreview.co.uk/forums/accent-insensitive-t3924592.html TLDR; está tudo bem :)
Jim W diz que restabelece Monica
on msdn: "O Padrão Unicode define caracteres combinados como caracteres combinados com caracteres base para produzir um novo caractere. Caracteres combinados não espaçadores não ocupam uma posição de espaçamento sozinhos quando renderizados."
Avlin
ok este método falhou por estes 2 strings: tarafli / TARAFLİ servidor no entanto SQL diz iguais como deveria ser
MonsterMMORPG
2
Isso ocorre porque geralmente o SQL Server é configurado para não diferenciar maiúsculas de minúsculas, mas, por padrão, as comparações em .Net diferenciam maiúsculas de minúsculas. Atualizei a resposta para mostrar como tornar esse caso insensível.
precisa saber é o seguinte
Estou tentando criar um IEqualityComparer. Ele precisa fornecer GetHashCode ... Como você consegue que (ele precisa ser o mesmo se for igual)
Yepeekai
5

O método a seguir CompareIgnoreAccents(...)funciona nos seus dados de exemplo. Aqui está o artigo em que obtive minhas informações básicas: http://www.codeproject.com/KB/cs/EncodingAccents.aspx

private static bool CompareIgnoreAccents(string s1, string s2)
{
    return string.Compare(
        RemoveAccents(s1), RemoveAccents(s2), StringComparison.InvariantCultureIgnoreCase) == 0;
}

private static string RemoveAccents(string s)
{
    Encoding destEncoding = Encoding.GetEncoding("iso-8859-8");

    return destEncoding.GetString(
        Encoding.Convert(Encoding.UTF8, destEncoding, Encoding.UTF8.GetBytes(s)));
}

Eu acho que um método de extensão seria melhor:

public static string RemoveAccents(this string s)
{
    Encoding destEncoding = Encoding.GetEncoding("iso-8859-8");

    return destEncoding.GetString(
        Encoding.Convert(Encoding.UTF8, destEncoding, Encoding.UTF8.GetBytes(s)));
}

Então o uso seria o seguinte:

if(string.Compare(s1.RemoveAccents(), s2.RemoveAccents(), true) == 0) {
   ...
Ryan Cook
fonte
1
isso faz carta acentuada para '?'
precisa saber é o seguinte
4
Esta é uma comparação destrutiva, onde, por exemplo, ā e ē serão tratados como iguais. Você perde caracteres acima de 0xFF e não há garantia de que as strings tenham o mesmo acento de ignorar.
Abel
Você também perde coisas como ñ. Não é uma solução se você me perguntar.
Ignacio Soler Garcia
5

Eu tive que fazer algo semelhante, mas com um método StartsWith. Aqui está uma solução simples derivada do @Serge - appTranslator.

Aqui está um método de extensão:

    public static bool StartsWith(this string str, string value, CultureInfo culture, CompareOptions options)
    {
        if (str.Length >= value.Length)
            return string.Compare(str.Substring(0, value.Length), value, culture, options) == 0;
        else
            return false;            
    }

E para um forros malucos;)

    public static bool StartsWith(this string str, string value, CultureInfo culture, CompareOptions options)
    {
        return str.Length >= value.Length && string.Compare(str.Substring(0, value.Length), value, culture, options) == 0;
    }

Início sem distinção entre maiúsculas e minúsculas e maiúsculas e minúsculas

value.ToString().StartsWith(str, CultureInfo.InvariantCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase)
Guish
fonte
0

Uma maneira mais simples de remover acentos:

    Dim source As String = "áéíóúç"
    Dim result As String

    Dim bytes As Byte() = Encoding.GetEncoding("Cyrillic").GetBytes(source)
    result = Encoding.ASCII.GetString(bytes)
Newton Carlos Dantas
fonte
-3

tente essa sobrecarga no método String.Compare.

Método String.Compare (String, String, Boolean, CultureInfo)

Ele produz um valor int com base nas operações de comparação, incluindo informações sobre a cultura. o exemplo na página compara "Alterar" em en-US e en-CZ. CH em en-CZ é uma única "letra".

exemplo do link

using System;
using System.Globalization;

class Sample {
    public static void Main() {
    String str1 = "change";
    String str2 = "dollar";
    String relation = null;

    relation = symbol( String.Compare(str1, str2, false, new CultureInfo("en-US")) );
    Console.WriteLine("For en-US: {0} {1} {2}", str1, relation, str2);

    relation = symbol( String.Compare(str1, str2, false, new CultureInfo("cs-CZ")) );
    Console.WriteLine("For cs-CZ: {0} {1} {2}", str1, relation, str2);
    }

    private static String symbol(int r) {
    String s = "=";
    if      (r < 0) s = "<";
    else if (r > 0) s = ">";
    return s;
    }
}
/*
This example produces the following results.
For en-US: change < dollar
For cs-CZ: change > dollar
*/

portanto, para idiomas acentuados, você precisará obter a cultura e testar as strings com base nisso.

http://msdn.microsoft.com/en-us/library/hyxc48dt.aspx


fonte
Essa é uma abordagem melhor do que comparar diretamente as strings, mas ainda considera a letra base e sua versão acentuada diferentes . Portanto, ele não responde à pergunta original, que queria que os acentos fossem ignorados.
CB