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ć?

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

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
}

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

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)

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)

-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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.