Ignorando las letras acentuadas en la comparación de cadenas

141

Necesito comparar 2 cadenas en C # y tratar las letras acentuadas de la misma manera que las letras sin acento. Por ejemplo:

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

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

Estas 2 cadenas deben ser las mismas (en lo que respecta a mi solicitud), pero ambas afirmaciones se evalúan como falsas. ¿Hay alguna manera en C # para hacer esto?

Jon Tackabury
fuente

Respuestas:

251

EDITAR 2012-01-20: ¡Oh chico! La solución fue mucho más simple y ha estado en el marco casi para siempre. Como señaló knightpfhor :

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

Aquí hay una función que elimina los signos diacríticos de una cadena:

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

Más detalles en el blog de MichKap ( RIP ... ).

El principio es que convierte 'é' en 2 caracteres sucesivos 'e', ​​agudo. Luego itera a través de los caracteres y omite los signos diacríticos.

"héllo" se convierte en "he <acute> llo", que a su vez se convierte en "hola".

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

Nota: Aquí hay una versión más compacta .NET4 + amigable de la misma función:

static string RemoveDiacritics(string text)
{
  return string.Concat( 
      text.Normalize(NormalizationForm.FormD)
      .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch)!=
                                    UnicodeCategory.NonSpacingMark)
    ).Normalize(NormalizationForm.FormC);
}
Serge Wautier
fuente
1
¿Cómo hacerlo en .net core ya que no tiene string.Normalize?
Andre Soares
Gracias por esto, ¡desearía poder votar más de una vez! Sin embargo, no maneja todas las letras acentuadas, por ejemplo, ð, ħ y ø no se convierten a o, h y o respectivamente. ¿Hay alguna manera de manejar esto también?
Avrohom Yisroel
@AvrohomYisroel el "ð" es una "letra pequeña latina Eth", que es una letra separada, no una "o con acento" o "d-con acento". Los otros son "Letra latina minúscula H con trazo" y "Letra latina minúscula O con trazo" que también pueden considerarse letras separadas
Hans Ke sting
135

Si no necesita convertir la cadena y solo desea verificar la igualdad, puede usar

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

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

o si quieres que la comparación no distinga entre mayúsculas y 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
}
caballero
fuente
Si alguien más tiene curiosidad acerca de esta opción IgnoreNonSpace, es posible que desee leer esta discusión al respecto. pcreview.co.uk/forums/accent-insensitive-t3924592.html TLDR; está bien :)
Jim W dice que reinstalen a Monica el
en msdn: "El estándar Unicode define la combinación de caracteres como caracteres que se combinan con los caracteres base para producir un nuevo carácter. Los caracteres de combinación sin espaciado no ocupan una posición de espaciado por sí mismos cuando se representan".
Avlin
ok, este método falló para estas 2 cadenas: tarafli / TARAFLİ, sin embargo, el servidor SQL dice que debería ser
igual
2
Esto se debe a que, en general, SQL Server está configurado para que no distinga entre mayúsculas y minúsculas, pero de forma predeterminada las comparaciones en .Net distinguen entre mayúsculas y minúsculas. He actualizado la respuesta para mostrar cómo hacer que este caso no sea sensible.
knightpfhor
Estoy tratando de crear un IEqualityComparer. Necesita proporcionar GetHashCode ... ¿Cómo se obtiene eso? (
Debe
5

El siguiente método CompareIgnoreAccents(...)funciona en sus datos de ejemplo. Aquí está el artículo donde obtuve mi información de fondo: 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)));
}

Creo que un método de extensión sería mejor:

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

Entonces el uso sería este:

if(string.Compare(s1.RemoveAccents(), s2.RemoveAccents(), true) == 0) {
   ...
Ryan Cook
fuente
1
esto hace que la letra acentuada a '?'
onmyway133
44
Esta es una comparación destructiva, donde, por ejemplo, ā y ē serán tratados como iguales. Pierdes todos los caracteres por encima de 0xFF y no hay garantía de que las cadenas tengan acentos de igual ignoración.
Abel
También pierdes cosas como ñ. No es una solución si me preguntas.
Ignacio Soler García
5

Tuve que hacer algo similar pero con un método StartsWith. Aquí hay una solución simple derivada de @Serge - appTranslator.

Aquí hay un método de extensión:

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

Y por un lado lindos;)

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

Incensivo de acento y comienzo de incienso sensible a mayúsculas y minúsculas Se puede llamar así

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

Una forma más simple de eliminar 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
fuente
-3

intente esta sobrecarga en el método String.Compare.

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

Produce un valor int basado en las operaciones de comparación, incluida cultureinfo. el ejemplo en la página compara "Cambiar" en en-US y en-CZ. CH en en-CZ es una sola "letra".

ejemplo del enlace

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
*/

por lo tanto, para los idiomas acentuados, necesitará obtener la cultura y luego probar las cadenas según eso.

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


fuente
Este es un mejor enfoque que comparar directamente las cadenas, pero aún considera que la letra base y su versión acentuada son diferentes . Por lo tanto, no responde a la pregunta original, que quería ignorar los acentos.
CB