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


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.