Unicode karakterlerinden aksan işaretlerini (ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ) kaldırın


88

Ben aksan ile (karakterler arasında eşleyebilir bir algoritma bakıyorum tilde , inceltme , şapka , umlaut , caron ) ve bunların "basit" karakteri.

Örneğin:

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

Vb.

  1. Bunu Java'da yapmak istiyorum, ancak bunun Unicode-y bir şey olması gerektiğinden ve herhangi bir dilde makul ölçüde kolayca yapılabilmesi gerektiğinden şüpheleniyorum.

  2. Amaç: Aksan işaretli kelimelerin kolayca aranmasına izin vermek. Örneğin, tenis oyuncuları veri tabanım varsa ve Björn_Borg girilmişse, Bjorn_Borg'u da tutacağım, böylece birisi Björn'e değil de Bjorn'a girerse bulabilirim.


Hangi ortamda programladığınıza bağlıdır, ancak muhtemelen manuel olarak bir tür eşleme tablosu tutmanız gerekecek. Peki hangi dili kullanıyorsun?
Thorarin

15
Lütfen ñ en.wikipedia.org/wiki/%C3%91 gibi bazı harflerin , arama amacıyla aksan işaretlerinin çıkarılmaması gerektiğine dikkat edin . Google, İspanyolca "ano" (anüs) ve "año" (yıl) arasında doğru bir ayrım yapar. Dolayısıyla, gerçekten iyi bir arama motoru istiyorsanız, temel aksan işareti kaldırmaya güvenemezsiniz.
Eduardo

@Eduardo: Belli bir bağlamda bu önemli olmayabilir. OP'nin verdiği örneği kullanarak, bir kişinin adını çok uluslu bir bağlamda aramak, aslında aramanın çok doğru olmamasını istersiniz.
Amir Abiri

(Yanlışlıkla önceden gönderilmiş) Fonetik aramayı iyileştirmek için aksanları fonetik eşdeğerleriyle eşlemek için yer var. yani, ñ => ni, temeldeki arama motoru fonetik tabanlı (ör. soundex) aramayı destekliyorsa daha iyi sonuçlar verecektir
Amir Abiri

Año'nun ano'ya değiştirildiği bir kullanım örneği, URL'ler, kimlikler vb. İçin base64 olmayan karakterlerin çıkarılmasıdır
Ondra Žižka

Yanıtlar:


83

Bunu yakın zamanda Java'da yaptım:

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

Bu, belirttiğiniz gibi yapacak:

stripDiacritics("Björn")  = Bjorn

ancak örneğin Białystok'ta başarısız olur, çünkü łkarakter aksan değildir.

Tam gelişmiş bir dizeyi basitleştirmek istiyorsanız, aksan olmayan bazı özel karakterler için ikinci bir temizleme turuna ihtiyacınız olacak. Bu harita mı, müşteri adlarımızda görünen en yaygın özel karakterleri ekledim. Tam bir liste değildir, ancak size onu nasıl genişleteceğiniz konusunda fikir verecektir. İmmutableMap, google-collections'tan sadece basit bir sınıftır.

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

ya ╨ gibi karakterler?
mickthompson

yine de geçecekler. aynı şekilde tüm japonca karakterler vs.
Andreas Petersson

teşekkürler Andreas. Bunları kaldırmanın bir yolu var mı? ら が な を 覚 男 (veya diğerleri) gibi karakterler oluşturulan dizgeye dahil edilecek ve bunlar temelde çıktıyı kesecek. StackOverflow'un Questions URL'leri için yaptığı gibi simplifiedString çıktısını bir URL oluşturucu olarak kullanmaya çalışıyorum.
mickthompson

2
Soru yorumunda söylediğim gibi. İyi bir arama motoru istiyorsanız, temel aksan işareti kaldırmaya güvenemezsiniz.
Eduardo

3
Teşekkürler Andreas, harika çalışıyor! :-) (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̌ǵß test)
Fortega

25

Çekirdek java.text paketi bu kullanım durumunu ele almak için tasarlanmıştır (dizeleri aksan işaretleri, büyük / küçük harf durumu vb. Umursamadan eşleştirme).

Karakterlerdeki farklılıklara göre Collatorsıralamak için a'yı yapılandırın PRIMARY. Bununla, CollationKeyher dize için bir tane oluşturun . Kodunuzun tamamı Java'daysa, CollationKeydoğrudan kullanabilirsiniz . Anahtarları bir veritabanında veya başka tür bir dizinde saklamanız gerekiyorsa, bunu bir bayt dizisine dönüştürebilirsiniz .

Bu sınıflar, hangi karakterlerin eşdeğer olduğunu belirlemek için Unicode standart büyük / küçük harf bölme verilerini kullanır ve çeşitli ayrıştırma stratejilerini destekler .

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"

Harmanlayıcıların yerel ayara özgü olduğunu unutmayın. Bunun nedeni, "alfabetik sıranın" yerel ayarlar arasında (ve hatta İspanyolca'da olduğu gibi zaman içinde) farklılık göstermesidir. CollatorSınıf Bu kuralların tümünü izlemek ve güncel onları tutmak zorunda hafifletir.


kulağa ilginç geliyor, ancak harmanlama anahtarınızı veritabanında select * 'dan harmanlanmış_adı' bjo% 'gibi olan kişiden arayabilir misiniz?
Andreas Petersson

çok güzel, bunu bilmiyordum. bunu deneyecek.
Andreas Petersson

Android'de CollationKeys, veritabanı aramaları için önek olarak kullanılamaz. Dizinin bir harmanlama anahtarı a41, 1, 5, 1, 5, 0 abbaytlarına dönüşürken , dizi 41, 43, 1, 6, 1, 6, 0 baytlarına dönüşür. Bu bayt dizileri olduğu gibi görünmez tam kelimelerle (harmanlama anahtarı aiçin bayt dizisi, harmanlama anahtarı için bayt dizisinde görünmez ab)
Grzegorz Adam Hankiewicz

1
@GrzegorzAdamHankiewicz Bazı testlerden sonra bayt dizilerinin karşılaştırılabildiğini ancak belirttiğiniz gibi önekler oluşturmadığını görüyorum. Bu nedenle, gibi bir önek sorgusu bjo%yapmak için, harmanlayıcıların> = bjove < olduğu bir aralık sorgusu gerçekleştirmeniz gerekir bjp(veya o yerel ayarda sonraki sembol ne olursa olsun ve bunu belirlemenin programlı bir yolu yoktur).
erickson

16

Versiyon itibariyle Apache Commons Lang'ın bir parçasıdır . 3.1.

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

İadeler An


1
Ø için tekrar verir Ø
Mike Argyriou

2
Bunu belirttiğin için teşekkürler Mike. Yöntem yalnızca aksanları ele alır. "Ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ" sonucunun sonucu "nnnnnnnnn ɲ ƞ ᶇ ɳ ȵ"
Kenston Choi

12

Normalizer sınıfını şuradan kullanabilirsiniz java.text:

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

Ancak Java, dönüştürülemeyen Unicode karakterlerle tuhaf şeyler yaptığından (onları görmezden gelmez ve bir istisna oluşturmaz) hala yapılması gereken bazı işler vardır. Ama bence bunu bir başlangıç ​​noktası olarak kullanabilirsin.


3
bu, rusça gibi ascii olmayan aksanlar için işe yaramayacak, aksanları da var ve ayrıca tüm Asya dizelerini parçalayacak. kullanmayın. ascii'ye dönüştürmek yerine, answer stackoverflow.com/questions/1453171/…
Andreas Petersson


5

Lütfen bu işaretlerin hepsinin, anlamı değiştirmeden kaldırabileceğiniz bazı "normal" karakterlerin "işaretleri" olmadığını unutmayın.

İsveççe'de å ä ve ö gerçek ve uygun birinci sınıf karakterlerdir, başka bir karakterin "varyantı" değildir. Diğer tüm karakterlerden farklı sesler çıkarırlar, farklı sıralarlar ve kelimelerin anlamlarını değiştirirler ("mätt" ve "matt" iki farklı kelimedir).


4
Doğru olmasına rağmen, bu sorunun cevabından çok bir yorumdur.
Simon Forsberg

2

Unicode, belirli diatrik karakterlere (bileşik karakterler) sahiptir ve karakter ve diatriklerin ayrılması için bir dize dönüştürülebilir. Ardından, diatrikleri dizeden kaldırabilirsiniz ve temelde bitirdiniz.

Normalleştirme, ayrıştırma ve denklik hakkında daha fazla bilgi için, Unicode ana sayfasındaki Unicode Standardına bakın .

Bununla birlikte, bunu nasıl başarabileceğiniz, üzerinde çalıştığınız çerçeveye / işletim sistemine / ... bağlıdır. .NET kullanıyorsanız , System.Text.NormalizationForm numaralandırmasını kabul eden String.Normalize yöntemini kullanabilirsiniz .


2
Bu, .NET'te kullandığım yöntemdir, ancak yine de bazı karakterleri elle eşlemem gerekiyor. Aksan değil, digraflar. Yine de benzer bir sorun.
Thorarin

1
Normalleştirme biçimine "D" (yani ayrıştırılmış) dönüştürün ve temel karakteri alın.
Richard

2

En kolay yol (benim için), Unicode kod noktalarınızı görüntülenebilir dizelere dönüştüren seyrek bir eşleme dizisi sağlamaktır.

Gibi:

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

Seyrek bir dizinin kullanılması , Unicode tablosunun geniş aralıklı bölümlerinde olsalar bile değiştirmeleri verimli bir şekilde temsil etmenize olanak sağlar. Dize değiştirmeleri, rastgele dizilerin aksanlarınızın yerini almasına izin verir ( ægrafem oluşumu gibi ae).

Bu, dilden bağımsız bir cevaptır, bu nedenle, aklınızda belirli bir dil varsa, daha iyi yollar olacaktır (yine de bunların hepsi muhtemelen en düşük seviyelerde buna inecektir).


Olası tüm garip karakterleri eklemek kolay bir iş değildir. Bunu yalnızca birkaç karakter için yaptığınızda, bu iyi bir çözümdür.
Simon Forsberg

2

Dikkate alınması gereken bir şey: Her kelimenin tek bir "çevirisini" almaya çalışırsanız, bazı olası alternatifleri kaçırabilirsiniz.

Örneğin, Almanca'da "s-set" yerine geçerken, bazı insanlar "B", diğerleri "ss" kullanabilir. Veya, noktalı bir o'nun "o" veya "oe" ile değiştirilmesi. Bulduğunuz herhangi bir çözüm, ideal olarak, ikisini de içermesi gerektiğini düşünüyorum.


2

Windows ve .NET'te, dize kodlamasını kullanarak dönüştürüyorum. Bu şekilde manuel haritalama ve kodlamadan kaçınırım.

Dize kodlamasıyla oynamaya çalışın.


3
Dize kodlamasını detaylandırır mısınız? Örneğin, bir kod örneği ile.
Peter Mortensen

2

Almanca söz konusu olduğunda Umlautlardan (ä, ö, ü) aksanların kaldırılması istenmez. Bunun yerine iki harf kombinasyonu ile değiştirilirler (ae, oe, ue) Örneğin, doğru telaffuza sahip olmak için Björn Bjoern (Bjorn değil) olarak yazılmalıdır.

Bunun için, her özel karakter grubu için değiştirme kuralını ayrı ayrı tanımlayabileceğiniz, kodlanmış bir eşleştirmeyi tercih ederim.


0

İleride başvurmak için, aksanları kaldıran bir C # genişletme yöntemi burada verilmiştir.

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");
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.