Ігнорування букв із наголосом у порівнянні рядків


141

Мені потрібно порівняти 2 рядки в C # і трактувати букви з наголосом так само, як і літери без наголосу. Наприклад:

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

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

Ці 2 рядки повинні бути однаковими (що стосується моєї програми), але обидва ці твердження оцінюються як хибні. Чи є в C # спосіб це зробити?

Відповіді:


251

EDIT 2012-01-20: О, хлопче! Рішення було набагато простішим і було в його рамках майже назавжди. Як вказував knightpfhor :

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

Ось функція, яка знімає діакритику з рядка:

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

Детальніше у блозі MichKap ( RIP ... ).

Принцип полягає в тому, що це перетворює 'é' на 2 послідовних символи 'e', ​​гострі. Потім воно перебирається через символи та пропускає діакритику.

"héllo" стає "he <acute> llo", що в свою чергу стає "привіт".

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

Примітка. Ось більш компактна .NET4 + зручна версія тієї ж функції:

static string RemoveDiacritics(string text)
{
  return string.Concat( 
      text.Normalize(NormalizationForm.FormD)
      .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch)!=
                                    UnicodeCategory.NonSpacingMark)
    ).Normalize(NormalizationForm.FormC);
}

1
Як це зробити в .net core, оскільки його немає string.Normalize?
Андре Соарес

Дякую за це, я б хотів, щоб я міг подати заявку не раз! Однак він не обробляє всі букви з наголосом, наприклад, ð, ħ і ø не перетворюються в o, h і o відповідно. Чи є також спосіб впоратися з ними?
Avrohom Yisroel

@AvrohomYisroel, "ð" - це "латинська маленька літера" Eth ", яка є окремою літерою, а не" наголосом-з-наголосом "або" d-з наголосом ". Інші - "Малі латинські літери Н з обведенням" та "Малі латинські літери" О штрихом ", які також можна вважати окремими літерами
Ганс Ке,

135

Якщо вам не потрібно перетворювати рядок, і ви просто хочете перевірити рівність, яку ви можете використовувати

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

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

або якщо ви хочете, щоб порівняння було також нечутливим до регістру

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

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

Якщо комусь цікаво цей варіант IgnoreNonSpace, ви можете прочитати цю дискусію на ньому. pcreview.co.uk/forums/accent-insensitive-t3924592.html TLDR; це добре :)
Джим W каже, що поверніть Моніку

on msdn: "Стандарт Unicode визначає поєднання символів як символів, які поєднуються з базовими символами для отримання нового символу. Нерозмінні символи, що поєднують, не займають проміжку позиції самостійно при візуалізації."
Авлін

ок, цей метод не вдався до цих двох рядків: tarafli / TARAFLİ, однак сервер SQL каже рівним, як і належить
MonsterMMORPG

2
Це тому, що зазвичай SQL Server налаштований на нечутливі регістри, але порівняння за замовчуванням у .Net залежно від регістру. Я оновив відповідь, щоб показати, як зробити цю справу нечутливою.
knightpfhor

Я намагаюся створити IEqualityComparer. Йому потрібно надати GetHashCode ... Як ви це отримаєте (це має бути однаково, якщо він рівний)
Yepeekai

5

Наступний метод CompareIgnoreAccents(...)працює на ваших прикладних даних. Ось стаття, де я отримав свою основну інформацію: 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)));
}

Я думаю, що метод розширення буде краще:

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

Тоді використання буде таким:

if(string.Compare(s1.RemoveAccents(), s2.RemoveAccents(), true) == 0) {
   ...

1
це робить лист з наголосом на "?"
onmyway133

4
Це деструктивне порівняння, коли, наприклад, ā і ē будуть розглядатися як рівні. Ви втрачаєте будь-які символи вище 0xFF і немає гарантії того, що рядки будуть рівними ігноруючими-наголосами.
Авель

Ви також втрачаєте такі речі, як ñ. Не рішення, якщо ви запитаєте мене.
Ігнасіо Солер Гарсія

5

Мені довелося зробити щось подібне, але методом StartsWith. Ось просте рішення, отримане від @Serge - appTranslator.

Ось метод розширення:

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

І за один лайнер виродки;)

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

Акцентивні інцеситивні та випадкові інцеситивні старти, З якими можна назвати так

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

0

Більш простий спосіб видалення акцентів:

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

    Dim bytes As Byte() = Encoding.GetEncoding("Cyrillic").GetBytes(source)
    result = Encoding.ASCII.GetString(bytes)

-3

спробуйте це перевантаження методом String.Compare.

Метод String.Compare (String, String, Boolean, CultureInfo)

Він створює значення int на основі операцій порівняння, включаючи cultureinfo. приклад на сторінці порівнює "Змінити" в en-US та en-CZ. CH в en-CZ - це одна «буква».

приклад із посилання

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

для цього на мовах наголосів потрібно отримати культуру, а потім протестувати рядки на основі цього.

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


Це кращий підхід, ніж безпосередньо порівняння рядків, але він все ще вважає основну букву та її акцентовану версію різною . Тому він не відповідає на початкове запитання, яке хотіло б ігнорувати наголоси.
КБ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.