Xóa các dấu phụ (ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ) khỏi bảng mã Unicode


88

Tôi đang tìm kiếm tại một thuật toán có thể lập bản đồ giữa các nhân vật với dấu ( dấu ngã , circumflex , caret , âm sắc , dấu mũ ngược ) và nhân vật "đơn giản" của họ.

Ví dụ:

ń  ǹ  ň  ñ  ṅ  ņ  ṇ  ṋ  ṉ  ̈  ɲ  ƞ ᶇ ɳ ȵ  --> n
á --> a
ä --> a
ấ --> a
ṏ --> o

Vân vân.

  1. Tôi muốn làm điều này bằng Java, mặc dù tôi nghi ngờ nó phải là một cái gì đó Unicode-y và có thể thực hiện một cách hợp lý dễ dàng bằng bất kỳ ngôn ngữ nào.

  2. Mục đích: cho phép dễ dàng tìm kiếm các từ có dấu phụ. Ví dụ: nếu tôi có cơ sở dữ liệu về những người chơi quần vợt và Björn_Borg được nhập, tôi cũng sẽ giữ Bjorn_Borg để tôi có thể tìm thấy nó nếu ai đó nhập Bjorn chứ không phải Björn.


Nó phụ thuộc vào môi trường bạn đang lập trình, mặc dù bạn có thể phải duy trì một số loại bảng ánh xạ theo cách thủ công. Vậy, bạn đang sử dụng ngôn ngữ nào?
Thorarin 21/09/09

15
Hãy lưu ý rằng một số chữ cái như ñ en.wikipedia.org/wiki/%C3%91 không được bỏ dấu phụ cho mục đích tìm kiếm. Google phân biệt chính xác giữa "ano" (hậu môn) trong tiếng Tây Ban Nha và "año" (năm). Vì vậy, nếu bạn thực sự muốn có một công cụ tìm kiếm tốt, bạn không thể dựa vào việc xóa dấu phụ cơ bản.
Eduardo

@Eduardo: Trong bối cảnh nhất định, điều đó có thể không quan trọng. Sử dụng ví dụ OP đã đưa ra, tìm kiếm tên của một người trong ngữ cảnh đa quốc gia, bạn thực sự muốn tìm kiếm không quá chính xác.
Amir Abiri

(Tình cờ được gửi trước) Mặc dù vậy vẫn có chỗ để ánh xạ các dấu phụ với các dấu tương đương ngữ âm của chúng để cải thiện việc tìm kiếm ngữ âm. tức là N => ni sẽ mang lại kết quả tốt hơn nếu các công cụ tìm kiếm hỗ trợ cơ bản phiên âm dựa trên (ví dụ Soundex) tìm kiếm
Amir Abiri

Một trường hợp sử dụng khi thay đổi año để ano vv được tước chars phi base64 cho URL, ID, vv
Ondra Žižka

Câu trả lời:


82

Tôi đã làm điều này gần đây trong Java:

public static final Pattern DIACRITICS_AND_FRIENDS
    = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");

private static String stripDiacritics(String str) {
    str = Normalizer.normalize(str, Normalizer.Form.NFD);
    str = DIACRITICS_AND_FRIENDS.matcher(str).replaceAll("");
    return str;
}

Điều này sẽ làm như bạn đã chỉ định:

stripDiacritics("Björn")  = Bjorn

nhưng nó sẽ không thành công trên ví dụ Białystok, vì łký tự không có dấu.

Nếu bạn muốn có một chuỗi đơn giản hóa toàn diện, bạn sẽ cần một vòng làm sạch thứ hai, đối với một số ký tự đặc biệt hơn không phải là dấu phụ. Đây có phải là bản đồ, tôi đã bao gồm các ký tự đặc biệt phổ biến nhất xuất hiện trong tên khách hàng của chúng tôi. Nó không phải là một danh sách đầy đủ, nhưng nó sẽ cung cấp cho bạn ý tưởng về cách mở rộng nó. ImmutableMap chỉ là một lớp đơn giản từ bộ sưu tập google.

public class StringSimplifier {
    public static final char DEFAULT_REPLACE_CHAR = '-';
    public static final String DEFAULT_REPLACE = String.valueOf(DEFAULT_REPLACE_CHAR);
    private static final ImmutableMap<String, String> NONDIACRITICS = ImmutableMap.<String, String>builder()

        //Remove crap strings with no sematics
        .put(".", "")
        .put("\"", "")
        .put("'", "")

        //Keep relevant characters as seperation
        .put(" ", DEFAULT_REPLACE)
        .put("]", DEFAULT_REPLACE)
        .put("[", DEFAULT_REPLACE)
        .put(")", DEFAULT_REPLACE)
        .put("(", DEFAULT_REPLACE)
        .put("=", DEFAULT_REPLACE)
        .put("!", DEFAULT_REPLACE)
        .put("/", DEFAULT_REPLACE)
        .put("\\", DEFAULT_REPLACE)
        .put("&", DEFAULT_REPLACE)
        .put(",", DEFAULT_REPLACE)
        .put("?", DEFAULT_REPLACE)
        .put("°", DEFAULT_REPLACE) //Remove ?? is diacritic?
        .put("|", DEFAULT_REPLACE)
        .put("<", DEFAULT_REPLACE)
        .put(">", DEFAULT_REPLACE)
        .put(";", DEFAULT_REPLACE)
        .put(":", DEFAULT_REPLACE)
        .put("_", DEFAULT_REPLACE)
        .put("#", DEFAULT_REPLACE)
        .put("~", DEFAULT_REPLACE)
        .put("+", DEFAULT_REPLACE)
        .put("*", DEFAULT_REPLACE)

        //Replace non-diacritics as their equivalent characters
        .put("\u0141", "l") // BiaLystock
        .put("\u0142", "l") // Bialystock
        .put("ß", "ss")
        .put("æ", "ae")
        .put("ø", "o")
        .put("©", "c")
        .put("\u00D0", "d") // All Ð ð from http://de.wikipedia.org/wiki/%C3%90
        .put("\u00F0", "d")
        .put("\u0110", "d")
        .put("\u0111", "d")
        .put("\u0189", "d")
        .put("\u0256", "d")
        .put("\u00DE", "th") // thorn Þ
        .put("\u00FE", "th") // thorn þ
        .build();


    public static String simplifiedString(String orig) {
        String str = orig;
        if (str == null) {
            return null;
        }
        str = stripDiacritics(str);
        str = stripNonDiacritics(str);
        if (str.length() == 0) {
            // Ugly special case to work around non-existing empty strings
            // in Oracle. Store original crapstring as simplified.
            // It would return an empty string if Oracle could store it.
            return orig;
        }
        return str.toLowerCase();
    }

    private static String stripNonDiacritics(String orig) {
        StringBuffer ret = new StringBuffer();
        String lastchar = null;
        for (int i = 0; i < orig.length(); i++) {
            String source = orig.substring(i, i + 1);
            String replace = NONDIACRITICS.get(source);
            String toReplace = replace == null ? String.valueOf(source) : replace;
            if (DEFAULT_REPLACE.equals(lastchar) && DEFAULT_REPLACE.equals(toReplace)) {
                toReplace = "";
            } else {
                lastchar = toReplace;
            }
            ret.append(toReplace);
        }
        if (ret.length() > 0 && DEFAULT_REPLACE_CHAR == ret.charAt(ret.length() - 1)) {
            ret.deleteCharAt(ret.length() - 1);
        }
        return ret.toString();
    }

    /*
    Special regular expression character ranges relevant for simplification -> see http://docstore.mik.ua/orelly/perl/prog3/ch05_04.htm
    InCombiningDiacriticalMarks: special marks that are part of "normal" ä, ö, î etc..
        IsSk: Symbol, Modifier see http://www.fileformat.info/info/unicode/category/Sk/list.htm
        IsLm: Letter, Modifier see http://www.fileformat.info/info/unicode/category/Lm/list.htm
     */
    public static final Pattern DIACRITICS_AND_FRIENDS
        = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");


    private static String stripDiacritics(String str) {
        str = Normalizer.normalize(str, Normalizer.Form.NFD);
        str = DIACRITICS_AND_FRIENDS.matcher(str).replaceAll("");
        return str;
    }
}

những ký tự như ╨ thì sao?
mickthompson

chúng sẽ được thông qua. tương tự như vậy tất cả các nhân vật Nhật Bản, vv
Andreas Petersson

cảm ơn Andreas. Có cách nào để loại bỏ chúng? Các ký tự như ら が な を 覚 男 (hoặc các ký tự khác) sẽ được đưa vào chuỗi được tạo và về cơ bản những ký tự này sẽ phá vỡ đầu ra. Tôi đang cố gắng sử dụng đầu ra SimplyString làm trình tạo URL như StackOverflow thực hiện cho các URL Câu hỏi của nó.
mickthompson

2
Như tôi đã nói trong bình luận câu hỏi. Bạn không thể dựa vào việc xóa dấu phụ cơ bản nếu bạn muốn có một công cụ tìm kiếm tốt.
Eduardo

3
Cảm ơn Andreas, hoạt động như một sự quyến rũ! (thử nghiệm trên rrrr̈r'ŕřttẗţỳỹẙy'yýÿŷpp̈sss̈s̊s's̸śŝŞşšddd̈ďd'ḑf̈f̸ggg̈g'ģqĝǧḧĥj̈j'ḱkk̈k̸ǩlll̈Łłẅẍcc̈c̊c'c̸Çççćĉčvv̈v'v̸bb̧ǹnn̈n̊n'ńņňñmmmm̈m̊m̌ǵß) :-)
Fortega

25

Gói java.text cốt lõi được thiết kế để giải quyết trường hợp sử dụng này (khớp các chuỗi mà không cần quan tâm đến dấu phụ, chữ hoa, v.v.).

Định cấu hình một Collatorđể sắp xếp PRIMARYsự khác biệt trong các ký tự. Cùng với đó, hãy tạo một CollationKeycho mỗi chuỗi. Nếu tất cả mã của bạn bằng Java, bạn có thể sử dụng CollationKeytrực tiếp. Nếu bạn cần lưu trữ các khóa trong cơ sở dữ liệu hoặc loại chỉ mục khác, bạn có thể chuyển đổi nó thành một mảng byte .

Các lớp này sử dụng dữ liệu gấp chữ hoa chữ thường chuẩn Unicode để xác định các ký tự nào là tương đương và hỗ trợ các chiến lược phân tách khác nhau .

Collator c = Collator.getInstance();
c.setStrength(Collator.PRIMARY);
Map<CollationKey, String> dictionary = new TreeMap<CollationKey, String>();
dictionary.put(c.getCollationKey("Björn"), "Björn");
...
CollationKey query = c.getCollationKey("bjorn");
System.out.println(dictionary.get(query)); // --> "Björn"

Lưu ý rằng các trình đối chiếu là theo ngôn ngữ cụ thể. Điều này là do "thứ tự bảng chữ cái" khác nhau giữa các ngôn ngữ (và thậm chí theo thời gian, như trường hợp của tiếng Tây Ban Nha). Các Collatorlớp học giúp bạn thoát khỏi việc phải theo dõi tất cả các quy tắc và giữ cho chúng được cập nhật.


nghe có vẻ thú vị, nhưng bạn có thể tìm kiếm khóa đối chiếu của mình trong cơ sở dữ liệu với select * từ người có collated_name như 'bjo%' không ??
Andreas Petersson 21/09/09

rất tốt, không biết về điều đó. sẽ thử điều này.
Andreas Petersson 21/09/09

Trên Android, CollationKeys không thể được sử dụng làm tiền tố để tìm kiếm cơ sở dữ liệu. Khóa đối chiếu của chuỗi abiến thành byte 41, 1, 5, 1, 5, 0, nhưng chuỗi abchuyển thành byte 41, 43, 1, 6, 1, 6, 0. Các chuỗi byte này không xuất hiện như hiện tại nói cách đầy đủ (mảng byte cho khóa đối chiếu akhông xuất hiện trong mảng byte cho khóa collation cho ab)
Grzegorz Adam Hankiewicz

1
@GrzegorzAdamHankiewicz Sau một số thử nghiệm, tôi thấy rằng các mảng byte có thể được so sánh, nhưng không tạo thành tiền tố, như bạn đã lưu ý. Vì vậy, để thực hiện một truy vấn tiền tố như bjo%, bạn cần thực hiện một truy vấn phạm vi trong đó các bộ đối chiếu là> = bjovà < bjp(hoặc bất kỳ ký hiệu tiếp theo nào trong ngôn ngữ đó và không có cách lập trình nào để xác định điều đó).
erickson

16

Nó là một phần của Apache Commons Lang kể từ phiên bản. 3.1.

org.apache.commons.lang3.StringUtils.stripAccents("Añ");

trả lại An


1
Đối với Ø nó mang lại một lần nữa Ø
Mike Argyriou

2
Cảm ơn Mike đã chỉ ra điều đó. Phương pháp chỉ xử lý dấu. Kết quả của "ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ" là "nnnnnnnnn ɲ ƞ ᶇ ɳ ȵ"
Kenston Choi

12

Bạn có thể sử dụng lớp Normalizer từ java.text:

System.out.println(new String(Normalizer.normalize("ń ǹ ň ñ ṅ ņ ṇ ṋ", Normalizer.Form.NFKD).getBytes("ascii"), "ascii"));

Nhưng vẫn còn một số việc phải làm, vì Java tạo ra những điều kỳ lạ với các ký tự Unicode không thể thay đổi (nó không bỏ qua chúng và không đưa ra một ngoại lệ). Nhưng tôi nghĩ bạn có thể sử dụng nó như một điểm khởi đầu.


3
điều này sẽ không hoạt động đối với các dấu không phải ascii, chẳng hạn như ở tiếng Nga, họ cũng có dấu, và hơn nữa là bán thịt tất cả các chuỗi châu Á. không được dùng. thay vì chuyển đổi thành ascii, hãy sử dụng \\ p {InComosystemDiacriticalMarks} regexp như trong answer stackoverflow.com/questions/1453171/…
Andreas Petersson 21/09/09

10

Có một báo cáo dự thảo về cách gấp ký tự trên trang web unicode có rất nhiều tài liệu liên quan. Xem cụ thể Mục 4.1. "Thuật toán gấp".

Đây là một cuộc thảo luận và thực hiện loại bỏ dấu phụ bằng cách sử dụng Perl.

Các câu hỏi SO hiện có này liên quan đến:


5

Xin lưu ý rằng không phải tất cả các dấu này chỉ là "dấu" trên một số ký tự "bình thường", bạn có thể loại bỏ mà không thay đổi ý nghĩa.

Trong tiếng Thụy Điển, å ä và ö là các ký tự hạng nhất đúng và phù hợp, không phải là một số "biến thể" của một số ký tự khác. Chúng nghe khác với tất cả các ký tự khác, chúng sắp xếp khác nhau và chúng làm cho các từ thay đổi nghĩa ("mätt" và "matt" là hai từ khác nhau).


4
Mặc dù đúng, đây chỉ là một bình luận hơn là một câu trả lời cho câu hỏi.
Simon Forsberg

2

Unicode có các ký tự số cụ thể (là các ký tự kết hợp) và một chuỗi có thể được chuyển đổi để ký tự và các ký tự số được tách biệt. Sau đó, bạn chỉ cần loại bỏ các diatricts khỏi chuỗi và về cơ bản bạn đã hoàn thành.

Để biết thêm thông tin về chuẩn hóa, phân tách và tính tương đương, hãy xem Tiêu chuẩn Unicode tại trang chủ Unicode .

Tuy nhiên, làm thế nào bạn thực sự có thể đạt được điều này phụ thuộc vào khuôn khổ / hệ điều hành / ... bạn đang làm việc. Nếu đang sử dụng .NET, bạn có thể sử dụng phương thức String.Normalize chấp nhận kiểu liệt kê System.Text.NormalizationForm .


2
Đây là phương pháp tôi sử dụng trong .NET, mặc dù tôi vẫn phải ánh xạ một số ký tự theo cách thủ công. Chúng không phải là dấu phụ, mà là dấu chấm. Vấn đề tương tự mặc dù.
Thorarin 21/09/09

1
Chuyển sang dạng chuẩn hóa "D" (tức là đã phân rã) và lấy ký tự cơ sở.
Richard

2

Cách dễ nhất (đối với tôi) là chỉ cần duy trì một mảng ánh xạ thưa thớt mà chỉ đơn giản là thay đổi các điểm mã Unicode của bạn thành các chuỗi có thể hiển thị.

Nhu la:

start    = 0x00C0
size     = 23
mappings = {
    "A","A","A","A","A","A","AE","C",
    "E","E","E","E","I","I","I", "I",
    "D","N","O","O","O","O","O"
}
start    = 0x00D8
size     = 6
mappings = {
    "O","U","U","U","U","Y"
}
start    = 0x00E0
size     = 23
mappings = {
    "a","a","a","a","a","a","ae","c",
    "e","e","e","e","i","i","i", "i",
    "d","n","o","o","o","o","o"
}
start    = 0x00F8
size     = 6
mappings = {
    "o","u","u","u","u","y"
}
: : :

Việc sử dụng một mảng thưa thớt sẽ cho phép bạn biểu diễn các thay thế một cách hiệu quả ngay cả khi chúng nằm trong các phần cách nhau rộng rãi của bảng Unicode. Thay thế chuỗi sẽ cho phép các chuỗi tùy ý thay thế các dấu phụ của bạn (chẳng hạn như ægrapheme trở thành ae).

Đây là một câu trả lời bất khả tri về ngôn ngữ, vì vậy, nếu bạn có một ngôn ngữ cụ thể trong đầu, sẽ có nhiều cách tốt hơn (mặc dù dù sao thì tất cả chúng đều có khả năng giải quyết vấn đề này ở mức thấp nhất).


Thêm tất cả các ký tự lạ có thể có không phải là một nhiệm vụ dễ dàng. Khi chỉ làm điều này cho một vài ký tự, đó là một giải pháp tốt.
Simon Forsberg

2

Một số điều cần xem xét: nếu bạn cố gắng nhận một bản "dịch" của mỗi từ, bạn có thể bỏ lỡ một số cách thay thế có thể.

Ví dụ: trong tiếng Đức, khi thay thế "s-set", một số người có thể sử dụng "B", trong khi những người khác có thể sử dụng "ss". Hoặc, thay thế o có âm sắc bằng "o" hoặc "oe". Bất kỳ giải pháp nào bạn đưa ra, lý tưởng nhất, tôi nghĩ nên bao gồm cả hai.


2

Trong Windows và .NET, tôi chỉ chuyển đổi bằng cách sử dụng mã hóa chuỗi. Bằng cách đó, tôi tránh lập bản đồ và mã hóa thủ công.

Cố gắng chơi với mã hóa chuỗi.


3
Bạn có thể nói rõ hơn về mã hóa chuỗi không? Ví dụ, với một ví dụ mã.
Peter Mortensen

2

Trong trường hợp tiếng Đức, không muốn loại bỏ các dấu phụ khỏi Umlauts (ä, ö, ü). Thay vào đó, chúng được thay thế bằng tổ hợp hai chữ cái (ae, oe, ue) Ví dụ: Björn nên được viết là Bjoern (không phải Bjorn) để có cách phát âm chính xác.

Đối với điều đó, tôi muốn có một ánh xạ mã cứng, nơi bạn có thể xác định quy tắc thay thế riêng cho từng nhóm ký tự đặc biệt.


0

Để tham khảo trong tương lai, đây là phương thức mở rộng C # loại bỏ dấu.

public static class StringExtensions
{
    public static string RemoveDiacritics(this string str)
    {
        return new string(
            str.Normalize(NormalizationForm.FormD)
                .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != 
                            UnicodeCategory.NonSpacingMark)
                .ToArray());
    }
}
static void Main()
{
    var input = "ŃŅŇ ÀÁÂÃÄÅ ŢŤţť Ĥĥ àáâãäå ńņň";
    var output = input.RemoveDiacritics();
    Debug.Assert(output == "NNN AAAAAA TTtt Hh aaaaaa nnn");
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.