Ignorowanie akcentowanych liter w porównaniu ciągów

141

Muszę porównać 2 ciągi w C # i traktować litery akcentowane tak samo, jak litery bez akcentu. Na przykład:

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

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

Te 2 ciągi muszą być takie same (jeśli chodzi o moją aplikację), ale oba te stwierdzenia dają wynik fałszywy. Czy istnieje sposób w C #, aby to zrobić?

Jon Tackabury
źródło

Odpowiedzi:

251

EDYCJA 2012-01-20: O rany! Rozwiązanie było o wiele prostsze i było w ramach prawie od zawsze. Jak zauważył knightpfhor :

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

Oto funkcja, która usuwa znaki diakrytyczne z ciągu znaków:

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

Więcej szczegółów na blogu MichKap ( RIP ... ).

Zasada jest taka, że ​​zamienia „é” w 2 kolejne znaki „e”, ostre. Następnie dokonuje iteracji przez znaki i pomija znaki diakrytyczne.

„héllo” staje się „he <acute> llo”, co z kolei staje się „hello”.

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

Uwaga: oto bardziej kompaktowa, przyjazna dla platformy .NET4 + wersja tej samej funkcji:

static string RemoveDiacritics(string text)
{
  return string.Concat( 
      text.Normalize(NormalizationForm.FormD)
      .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch)!=
                                    UnicodeCategory.NonSpacingMark)
    ).Normalize(NormalizationForm.FormC);
}
Serge Wautier
źródło
1
Jak to zrobić w .net core, skoro go nie ma string.Normalize?
Andre Soares
Dzięki za to, chciałbym móc zagłosować więcej niż raz! Jednak nie obsługuje wszystkich liter akcentowanych, na przykład ð, ħ i ø nie są konwertowane odpowiednio na o, h i o. Czy jest jakiś sposób, aby sobie z tym poradzić?
Avrohom Yisroel
@AvrohomYisroel „ð” to „łacińska mała litera Eth”, która jest oddzielną literą, a nie „o-z-akcentem” ani „d-z-akcentem”. Pozostałe to „Łacińska mała litera H z udarem” i „Łacińska mała litera O z udarem”, które można również uznać za oddzielne litery
Hans Ke
135

Jeśli nie musisz konwertować ciągu i chcesz po prostu sprawdzić równość, możesz użyć

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

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

lub jeśli chcesz, aby porównanie również nie uwzględniało wielkości liter

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

if (String.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase) == 0)
{
    // both strings are equal
}
knightpfhor
źródło
Jeśli ktoś jest ciekawy tej opcji IgnoreNonSpace, możesz przeczytać tę dyskusję na jej temat. pcreview.co.uk/forums/accent-insensitive-t3924592.html TLDR; jest ok :)
Jim W mówi, że przywróć Monikę
on msdn: "Standard Unicode definiuje łączenie znaków jako znaków, które są łączone ze znakami podstawowymi w celu utworzenia nowego znaku. Łączone znaki bez odstępów nie zajmują samodzielnie pozycji odstępu podczas renderowania."
Avlin
ok, ta metoda nie powiodła się dla tych 2 ciągów: tarafli / TARAFLİ, jednak serwer SQL mówi, że jest równy
MonsterMMORPG
2
Dzieje się tak, ponieważ ogólnie SQL Server jest skonfigurowany tak, aby nie rozróżniać wielkości liter, ale domyślnie porównania w .Net uwzględniają wielkość liter. Zaktualizowałem odpowiedź, aby pokazać, jak uczynić tę wielkość niewrażliwą.
knightpfhor
Próbuję utworzyć IEqualityComparer. Musi dostarczyć GetHashCode ... Jak to uzyskać (musi być taki sam, jeśli jest równy)
Yepeekai
5

Poniższa metoda CompareIgnoreAccents(...)działa na przykładowych danych. Oto artykuł, w którym uzyskałem informacje ogólne : 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)));
}

Myślę, że metoda rozszerzenia byłaby lepsza:

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

Wtedy zastosowanie byłoby takie:

if(string.Compare(s1.RemoveAccents(), s2.RemoveAccents(), true) == 0) {
   ...
Ryan Cook
źródło
1
w ten sposób litera akcentowana na „?”
onmyway133
4
Jest to destrukcyjne porównanie, w którym na przykład ā i ē będą traktowane jako równe. Tracisz wszystkie znaki powyżej 0xFF i nie ma gwarancji, że ciągi są równe z ignorującymi akcentami.
Abel
Tracisz również takie rzeczy jak ñ. Nie jest rozwiązaniem, jeśli o mnie chodzi.
Ignacio Soler Garcia
5

Musiałem zrobić coś podobnego, ale z metodą StartsWith. Oto proste rozwiązanie pochodzące z @Serge - appTranslator.

Oto metoda rozszerzenia:

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

A dla jednego maniaków liniowców;)

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

Akcenty nieuwzględniające znaków i wielkości liter zaczynają się w ten sposób

value.ToString().StartsWith(str, CultureInfo.InvariantCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase)
Guish
źródło
0

Prostszy sposób na usunięcie akcentów:

    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
źródło
-3

spróbuj tego przeciążenia w metodzie String.Compare.

Metoda String.Compare (String, String, Boolean, CultureInfo)

Tworzy wartość int na podstawie operacji porównania, w tym cultureinfo. przykład na stronie porównuje „Zmiana” w językach en-US i en-CZ. CH w en-CZ to pojedyncza „litera”.

przykład z linku

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

dlatego w przypadku języków akcentowanych będziesz musiał pobrać kulturę, a następnie przetestować na jej podstawie napisy.

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


źródło
Jest to lepsze podejście niż bezpośrednie porównywanie ciągów, ale nadal uważa, że ​​podstawowa litera i jej akcentowana wersja są inne . Dlatego nie odpowiada na pierwotne pytanie, które chciało zignorować akcenty.
CB