删除变音符号(ǹňñ)ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳȵ)来自unicode字符

ha5z0ras  于 2021-08-20  发布在  Java
关注(0)|答案(12)|浏览(274)

我正在研究一种算法,它可以在带有变音符号(波浪符号、扬抑符号、插入符号、乌姆劳特符号、卡隆符号)的字符和它们的“简单”字符之间进行Map。
例如:

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


我想在java中实现这一点,尽管我怀疑它应该是unicode-y的,并且在任何语言中都应该可以相当容易地实现。
目的:允许轻松搜索带有变音符号的单词。例如,如果我有一个网球运动员的数据库,并且输入了比约恩·博格,我也会保留比约恩·博格,这样如果有人输入比约恩而不是比约恩,我就可以找到它。

omhiaaxx

omhiaaxx1#

我最近在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上失败,因为 ł 字符不是变音符号。
如果你想拥有一个完整的字符串简化程序,你需要第二轮清理,对于一些非变音符号的特殊字符。在这张Map上,我列出了出现在客户姓名中的最常见的特殊字符。这不是一个完整的列表,但它会告诉你如何扩展它。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;
    }
}
zlhcx6iw

zlhcx6iw2#

核心java.text包就是为了解决这个用例而设计的(匹配字符串而不考虑发音符号、大小写等)。
配置 Collator 分类 PRIMARY 性格上的差异。这样,就可以创建一个 CollationKey 对于每个字符串。如果您的所有代码都是java,那么可以使用 CollationKey 直接的。如果需要将键存储在数据库或其他类型的索引中,可以将其转换为字节数组。
这些类使用unicode标准大小写折叠数据来确定哪些字符是等效的,并支持各种分解策略。

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 类使您无需跟踪所有这些规则并使其保持最新。

xpcnnkqh

xpcnnkqh3#

它是apache commons lang的一部分。3.1.

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

返回 An

h22fl7wq

h22fl7wq4#

您可以使用 java.text :

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

但是仍然有一些工作要做,因为java使用不可转换的unicode字符生成奇怪的东西(它不会忽略它们,也不会抛出异常)。但我想你可以以此为出发点。

6rqinv9w

6rqinv9w5#

unicode网站上有一份关于字符折叠的报告草稿,里面有很多相关资料。具体见第4.1节。”折叠算法”。
下面是使用perl删除变音标记的讨论和实现。
这些现有的so问题是相关的:
如何将utf-8转换为美国ascii码
如何将变音字符改为非变音字符

fafcakar

fafcakar6#

请注意,并非所有这些标记都只是一些“普通”字符上的“标记”,您可以在不改变其含义的情况下删除这些标记。
在瑞典语中,åä和ö是真实而恰当的一级角色,而不是其他角色的“变体”。它们听起来不同于所有其他字符,排序也不同,它们使单词改变了意思(“mätt”和“matt”是两个不同的单词)。

0md85ypi

0md85ypi7#

unicode具有特定的字符(即复合字符),可以转换字符串,从而将字符和字符分开。然后,你可以从绳子上取下透明片,基本上就完成了。
有关规范化、分解和等价性的更多信息,请参阅unicode主页上的unicode标准。
然而,如何真正实现这一点取决于框架/os/。。。你正在努力。如果使用的是.net,则可以使用接受system.text.normalizationform枚举的string.normalize方法。

bqjvbblv

bqjvbblv8#

(对我来说)最简单的方法就是简单地维护一个稀疏Map数组,它可以简单地将unicode代码点更改为可显示的字符串。
例如:

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

使用稀疏数组将允许您高效地表示替换,即使它们位于unicode表的大间距部分中。字符串替换将允许任意序列替换变音符号(例如 æ 我变得 ae ).
这是一个语言不可知论的答案,因此,如果你心中有一种特定的语言,将会有更好的方法(尽管它们都可能在最低级别上实现)。

kzipqqlq

kzipqqlq9#

需要考虑的事情:如果你试图获得每个单词的一个“翻译”,你可能会错过一些可能的替代品。
例如,在德语中,当替换“s-set”时,一些人可能使用“b”,而另一些人可能使用“ss”。或者,用“o”或“oe”替换已加载的o。我认为,理想情况下,您提出的任何解决方案都应该包括这两个方面。

2lpgd968

2lpgd96810#

在windows和.net中,我只是使用字符串编码进行转换。这样我就避免了手动Map和编码。
尝试使用字符串编码。

gwbalxhn

gwbalxhn11#

在德语的例子中,它不想从umlauts(ä,ö,ü)中删除变音符号。取而代之的是两个字母的组合(ae、oe、ue),例如,Bjourn应该写成Bjourn(而不是bjorn),以便有正确的发音。
为此,我希望有一个硬编码Map,您可以为每个特殊字符组分别定义替换规则。

hfsqlsce

hfsqlsce12#

为了将来的参考,这里有一个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");
}

相关问题