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?

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

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
}

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) {
   ...

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)

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)

-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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.