ละเว้นตัวอักษรที่เน้นเสียงในการเปรียบเทียบสตริง


141

ฉันต้องเปรียบเทียบ 2 สายอักขระใน C # และปฏิบัติต่อตัวอักษรที่เน้นเสียงเหมือนกับตัวอักษรที่ไม่เน้นเสียง ตัวอย่างเช่น:

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

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

2 สตริงเหล่านี้จำเป็นต้องเหมือนกัน (เท่าที่เกี่ยวข้องกับแอปพลิเคชันของฉัน), แต่ข้อความทั้งสองนี้ประเมินว่าเป็นเท็จ มีวิธีใน C # ที่จะทำเช่นนี้?

คำตอบ:


251

แก้ไข 2012-01-20: Oh boy! การแก้ปัญหานั้นง่ายกว่ามากและอยู่ในกรอบเกือบตลอดกาล ตามที่ชี้โดย 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" กลายเป็น "เขา <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?
Andre Soares

ขอบคุณสำหรับสิ่งนี้ฉันหวังว่าฉันจะโหวตได้มากกว่าหนึ่งครั้ง! อย่างไรก็ตามมันไม่ได้จัดการกับตัวอักษรที่เน้นเสียงทั้งหมดเช่นð, ħและøไม่ได้ถูกแปลงเป็น o, h และ o ตามลำดับ มีวิธีจัดการกับสิ่งเหล่านี้ด้วยหรือไม่?
Avrohom Yisroel

@AvrohomYisroel "ð" เป็น "Latin Small Letter Eth" ซึ่งเป็นจดหมายแยกต่างหากไม่ใช่ "o-with-accent" หรือ "d-with-accent" คนอื่น ๆ ก็คือ "ตัวอักษรละตินตัวเล็ก H ด้วยโรคหลอดเลือดสมอง" และ "ตัวอักษรละตินตัวเล็ก O ด้วยโรคหลอดเลือดสมอง" ที่อาจถูกพิจารณาว่าเป็นตัวอักษรที่แยกจากกัน
Hans Ke st ing

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; ไม่เป็นไร :)
Jim W พูดว่าคืนสถานะโมนิก้า

บน msdn: "มาตรฐานยูนิโคดกำหนดการรวมอักขระเป็นอักขระที่รวมกับอักขระพื้นฐานเพื่อสร้างอักขระใหม่การรวมอักขระที่ไม่มีระยะห่างเข้าด้วยกันจะไม่ใช้ตำแหน่งระยะห่างด้วยตนเองเมื่อแสดงผล"
Avlin

ตกลงวิธีนี้ล้มเหลวสำหรับทั้งสองสาย: tarafli / TARAFLİ แต่เซิร์ฟเวอร์ SQL บอกว่าเท่าที่ควรจะเป็น
MonsterMMORPG

2
นั่นเป็นเพราะโดยทั่วไปแล้ว SQL Server จะได้รับการกำหนดค่าให้ไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ แต่โดยค่าเริ่มต้นการเปรียบเทียบใน. Net จะคำนึงถึงขนาดตัวพิมพ์ ฉันได้อัปเดตคำตอบเพื่อแสดงวิธีทำให้กรณีนี้ไม่รู้สึก
knightpfhor

ฉันกำลังพยายามสร้าง IEqualityComparer มันต้องให้ GetHashCode ... คุณจะได้รับมันอย่างไร (มันจะต้องเหมือนกันถ้ามันเท่ากัน)
34919

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 และไม่รับประกันว่า
Abel

คุณสูญเสียสิ่งต่างๆเช่นñ ไม่ใช่วิธีแก้ปัญหาถ้าคุณถามฉัน
Ignacio Soler Garcia

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 Method (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


นี้เป็นวิธีการที่ดีกว่าเมื่อเปรียบเทียบโดยตรงสตริง แต่ก็ยังคงพิจารณาจดหมายฐานและรุ่นของสำเนียงที่แตกต่างกัน ดังนั้นจึงไม่ตอบคำถามเดิมซึ่งต้องการให้เน้นการเน้น
CB
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.