유니 코드 문자에서 분음 부호 제거 (ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ)


88

분음 부호 ( 틸드 , 곡절 , 캐럿 , 움라우트 , 카론 )와 "단순"문자 사이를 매핑 할 수있는 알고리즘을 찾고 있습니다 .

예를 들면 :

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

기타.

  1. 나는 이것이 유니 코드 형식이어야하고 어떤 언어로도 합리적으로 쉽게 할 수 있어야한다고 생각하지만 자바로 이것을하고 싶다.

  2. 목적 : 분음 부호가있는 단어를 쉽게 검색 할 수 있습니다. 예를 들어, 테니스 선수 데이터베이스가 있고 Björn_Borg가 입력 된 경우 Bjorn_Borg도 유지하므로 Björn이 아닌 누군가가 Bjorn에 입력하면 찾을 수 있습니다.


어떤 종류의 매핑 테이블을 수동으로 유지해야 할 수도 있지만 프로그래밍중인 환경에 따라 다릅니다. 그래서 어떤 언어를 사용하고 있습니까?
— Thorarin

15
ñ en.wikipedia.org/wiki/%C3%91 과 같은 일부 문자 는 검색 목적으로 분음 부호를 제거해서는 안됩니다. Google은 스페인어 "ano"(anus)와 "año"(year)를 올바르게 구분합니다. 따라서 정말 좋은 검색 엔진을 원한다면 기본적인 분음 부호 제거에 의존 할 수 없습니다.
— Eduardo

@Eduardo : 주어진 상황에서 중요하지 않을 수도 있습니다. OP가 제공 한 예를 사용하여 다국적 컨텍스트에서 사람의 이름을 검색하면 실제로 검색이 너무 정확하지 않기를 원합니다.
— Amir Abiri

(실수로 이전에 전송 됨) 음성 검색을 개선하기 위해 분음 부호를 음성 등가물에 매핑 할 여지가 있습니다. 즉, N => NI 더 나은 결과를 얻을 것입니다 기본 검색 엔진에서 지원하는 검색 (예 : SOUNDEX) 음성은 기반 경우
— 아미르 Abiri

año를 ano 등으로 변경하는 사용 사례는 URL, ID 등에 대해 base64가 아닌 문자를 제거하는 것입니다.
— Ondra Žižka

답변:


82

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

지정한대로 수행됩니다.

stripDiacritics("Björn")  = Bjorn

그러나 ł문자가 분음 부호가 아니기 때문에 예를 들어 Białystok에서는 실패합니다 .

완전한 문자열 단순화를 원하면 분음 부호가 아닌 특수 문자에 대해 두 번째 정리 라운드가 필요합니다. 이 맵은 고객 이름에 나타나는 가장 일반적인 특수 문자를 포함했습니다. 완전한 목록은 아니지만 확장하는 방법에 대한 아이디어를 제공합니다. immutableMap은 google-collections의 단순한 클래스입니다.

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

╨ 같은 캐릭터는 어떻습니까?
— mickthompson 2010 년

그들은 통과 될 것입니다. 마찬가지로 모든 일본어 문자 등
— Andreas Petersson

감사합니다 Andreas. 이를 제거하는 방법이 있습니까? ら が な を 覚 男 (또는 기타)와 같은 문자가 생성 된 문자열에 포함되고 기본적으로 출력이 중단됩니다. StackOverflow가 질문의 URL에 대해 수행하는 것처럼 simpleString 출력을 URL 생성기로 사용하려고합니다.
— mickthompson 2010 년

2
내가 질문 코멘트에서 말했듯이. 좋은 검색 엔진을 원한다면 기본적인 분음 부호 제거에 의존 할 수 없습니다.
— Eduardo

3
감사합니다 Andreas, 매력처럼 작동합니다! :-) (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

핵심 java.text 패키지는이 사용 사례 (분음 부호, 대소 문자 등을 고려하지 않고 문자열 일치)를 해결하도록 설계되었습니다.

문자 차이 Collator를 정렬하도록 a 를 구성합니다 PRIMARY. 이를 통해 CollationKey각 문자열에 대해 생성하십시오 . 모든 코드가 Java로되어있는 경우 CollationKey직접 사용할 수 있습니다. 데이터베이스 나 다른 종류의 인덱스에 키를 저장해야하는 경우 이를 바이트 배열로 변환 할 수 있습니다 .

이러한 클래스는 유니 코드 표준을 사용합니다. 케이스 폴딩 데이터를 동일한 문자를 결정하고 다양한 분해 전략을 지원 합니다.

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"

콜 레이터는 로케일에 따라 다릅니다. 이것은 "알파벳 순서"가 로케일마다 다르기 때문입니다 (스페인어의 경우처럼 시간이 지남에 따라). Collator클래스는이 규칙을 모두 추적하고 최신을 유지하는 데에서 당신을 해소.


흥미롭게 들리지만 'bjo %'같은 collated_name?
— Andreas Petersson

아주 좋아, 그것에 대해 몰랐습니다. 이것을 시도 할 것입니다.
— Andreas Petersson

Android에서는 CollationKeys를 데이터베이스 검색의 접두사로 사용할 수 없습니다. 문자열의 조합 키는 a바이트 41, 1, 5, 1, 5, 0 ab으로 바뀌지 만 문자열 은 바이트 41, 43, 1, 6, 1, 6, 0으로 바뀝니다. 이러한 바이트 시퀀스는 그대로 나타나지 않습니다. 전체 단어 (조합 키의 바이트 배열 a에 대한 정렬 키의 바이트 배열에 나타나지 않습니다 ab)
— 그르 아담 Hankiewicz

1
@GrzegorzAdamHankiewicz 몇 가지 테스트 후 바이트 배열을 비교할 수 있지만 언급했듯이 접두사를 형성하지 않습니다. 따라서와 같은 접두사 쿼리 bjo%를 수행하려면 콜 레이터가> = bjo및 < bjp(또는 다음 기호가 해당 로케일에 있는 범위 쿼리를 수행해야하며 이를 결정할 수있는 프로그래밍 방식이 없음)을 수행해야합니다.
— erickson

16

이 버전 은 Apache Commons Lang 의 일부입니다 . 3.1.

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

보고 An


1
Ø 것이 Ø 다시 제공
— 마이크 Argyriou

2
지적 해주신 Mike에게 감사드립니다. 이 메서드는 액센트 만 처리합니다. "N ǹ N N N N N N N ̈ ɲ ƞ ᶇ ɳ ȵ"입니다 "nnnnnnnnn ɲ ƞ ᶇ ɳ ȵ"의 결과
— Kenston 최

12

다음에서 Normalizer 클래스 를 사용할 수 있습니다 java.text.

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

그러나 Java는 변환 할 수없는 유니 코드 문자로 이상한 일을 만들기 때문에 여전히해야 할 일이 있습니다 (이 문자를 무시하지 않고 예외를 던지지 않습니다). 그러나 나는 그것을 시작점으로 사용할 수 있다고 생각합니다.


3
이것은 러시아어와 같이 비 ASCII 분음 부호에는 작동하지 않으며 분음 부호도 있으며 모든 아시아 문자열을 도살합니다. 사용하지 마세요. 대신 응답 같이 ASCII로 사용 \\ P {InCombiningDiacriticalMarks} 정규 표현식을 변환 stackoverflow.com/questions/1453171/...
— 안드레아스 피터슨


5

이러한 모든 표시가 의미를 변경하지 않고 제거 할 수있는 일부 "일반"문자의 "표시"는 아닙니다.

스웨덴어에서 å ä와 ö는 다른 문자의 일부 "변형"이 아니라 참되고 적절한 일류 문자입니다. 그들은 다른 모든 문자들과 다르게 들리고, 다르게 분류되며, 단어의 의미를 변경합니다 ( "mätt"와 "matt"는 서로 다른 두 단어입니다).


4
정확하지만 이것은 질문에 대한 답변 이라기보다는 의견에 가깝습니다.
— Simon Forsberg 2013

2

유니 코드에는 특정 통어 문자 (복합 문자)가 있으며 문자와 통어가 분리되도록 문자열을 변환 할 수 있습니다. 그런 다음 문자열에서 구절을 제거하면 기본적으로 완료됩니다.

정규화, 분해 및 동등성에 대한 자세한 내용은 유니 코드 홈 페이지 에서 유니 코드 표준을 참조하십시오 .

그러나 실제로이를 달성하는 방법은 작업중인 프레임 워크 / OS / ...에 따라 다릅니다. .NET을 사용하는 경우 System.Text.NormalizationForm 열거를 허용하는 String.Normalize 메서드를 사용할 수 있습니다 .


2
이 방법은 .NET에서 사용하는 방법이지만 여전히 일부 문자를 수동으로 매핑해야합니다. 그들은 분음 부호가 아니라 digraphs입니다. 그래도 비슷한 문제.
— Thorarin

1
정규화 형식 "D"(즉, 분해됨)로 변환하고 기본 문자를 사용합니다.
— Richard

2

(나에게) 가장 쉬운 방법은 단순히 유니 코드 코드 포인트를 표시 가능한 문자열로 변경하는 희소 매핑 배열을 유지하는 것입니다.

예 :

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"
}
: : :

a의 사용 스파 스 배열 할 때 유니 코드 테이블의 널리 간격 섹션에서 효율적도 교체를 나타낼 수 있습니다. 문자열 대체는 임의의 시퀀스가 ​​분음 부호를 대체 할 수 있도록합니다 (예 : æ자소가 ae).

이것은 언어에 구애받지 않는 답변이므로 특정 언어를 염두에두면 더 좋은 방법이있을 것입니다 (어쨌든 가장 낮은 수준에서이 수준으로 내려갈 가능성이 높습니다).


가능한 모든 이상한 문자를 추가하는 것은 쉬운 일이 아닙니다. 몇 개의 문자에 대해서만이 작업을 수행 할 때 좋은 솔루션입니다.
— Simon Forsberg 2013

2

고려할 사항 : 각 단어의 단일 "번역"을 얻으려는 경로를 가면 가능한 대체 단어를 놓칠 수 있습니다.

예를 들어, 독일어에서 "s-set"를 대체 할 때 어떤 사람들은 "B"를 사용하고 다른 사람들은 "ss"를 사용할 수 있습니다. 또는 움라우트 o를 "o"또는 "oe"로 대체합니다. 이상적으로는 모든 솔루션을 모두 포함해야한다고 생각합니다.


2

Windows와 .NET에서는 문자열 인코딩을 사용하여 변환합니다. 그렇게하면 수동 매핑과 코딩을 피할 수 있습니다.

문자열 인코딩을 사용해보십시오.


3
문자열 인코딩에 대해 자세히 설명해 주시겠습니까? 예를 들어, 코드 예제가 있습니다.
— Peter Mortensen 2012

2

독일어의 경우 Umlauts (ä, ö, ü)에서 분음 부호를 제거하는 것을 원하지 않습니다. 대신 두 글자 조합 (ae, oe, ue)으로 대체됩니다. 예를 들어, Björn은 올바른 발음을 갖기 위해 Bjoern (Bjorn이 아님)으로 작성되어야합니다.

이를 위해 각 특수 문자 그룹에 대해 개별적으로 대체 규칙을 정의 할 수있는 하드 코딩 된 매핑을 사용합니다.


0

나중에 참조 할 수 있도록 악센트를 제거하는 C # 확장 메서드가 있습니다.

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");
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.