Удалить диакритические знаки (ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ɳ ȵ ȵ) из символов Unicode
Я смотрю на алгоритм, который может отображать между символами с диакритическими знаками ( тильда, круговое сгибание, карет, умлаут, карон) и их "простым" характером.
Например:
ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ --> n
á --> a
ä --> a
ấ --> a
ṏ --> o
И т.п.
Я хочу сделать это в Java, хотя я подозреваю, что это должно быть что-то Unicode-y и должно быть достаточно легко выполнимо на любом языке.
Цель: облегчить поиск слов с диакритическими знаками. Например, если у меня есть база данных теннисистов и введен Björn_Borg, я также сохраню Bjorn_Borg, чтобы найти его, если кто-то войдет в Bjorn, а не в Björn.
12 ответов
Я сделал это недавно в 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
но это потерпит неудачу, например, Белосток, потому что ł
характер не диакритический.
Если вы хотите иметь полномасштабный упрощатель строк, вам понадобится второй раунд очистки, для некоторых специальных символов, которые не являются диакритическими. В эту карту я включил наиболее распространенные специальные символы, которые появляются в именах наших клиентов. Это не полный список, но он даст вам представление о том, как его расширить. ImmutableMap - это простой класс из 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;
}
}
Базовый пакет 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
Класс освобождает вас от необходимости отслеживать все эти правила и поддерживать их в актуальном состоянии.
Это часть Apache Commons Lang от вер. 3.1.
org.apache.commons.lang3.StringUtils.stripAccents("Añ");
возвращается An
Вы можете использовать класс Normalizer из java.text
:
System.out.println(new String(Normalizer.normalize("ń ǹ ň ñ ṅ ņ ṇ ṋ", Normalizer.Form.NFKD).getBytes("ascii"), "ascii"));
Но предстоит еще проделать определенную работу, поскольку Java создает странные вещи с помощью необратимых символов Юникода (она не игнорирует их и не создает исключение). Но я думаю, что вы могли бы использовать это в качестве отправной точки.
На веб-сайте Unicode есть черновой отчет о свертывании символов, в котором есть много соответствующего материала. Смотрите конкретно раздел 4.1. "Алгоритм складывания".
Вот обсуждение и реализация удаления диакритического маркера с использованием Perl.
Эти существующие вопросы SO связаны:
Обратите внимание, что не все эти отметки являются просто "отметками" на каком-то "нормальном" символе, который вы можете удалить, не меняя значения.
На шведском языке ä ä и ö - настоящие и правильные символы первого класса, а не какой-то "вариант" какого-либо другого символа. Они звучат иначе, чем все остальные символы, сортируют по-разному и заставляют слова менять значение ("mätt" и "matt" - это два разных слова).
В случае с немецким языком не требуется удалять диакритические знаки из умлаутов (ä, ö, ü). Вместо этого они заменяются двухбуквенной комбинацией (ae, oe, ue). Например, Björn должен быть написан как Bjoern (не Bjorn), чтобы иметь правильное произношение.
Для этого у меня было бы довольно жесткое отображение, где вы можете определить правило замены индивидуально для каждой специальной группы символов.
В Windows и.NET я просто конвертирую, используя строковое кодирование. Таким образом я избегаю ручного отображения и кодирования.
Попробуйте поиграть со строковым кодированием.
Unicode имеет определенные диатрические символы (которые являются составными символами), и строку можно преобразовать так, чтобы символ и диатрики были разделены. Затем вы можете просто удалить указатели из строки, и вы в основном сделали.
Для получения дополнительной информации о нормализации, декомпозиции и эквивалентности см. Стандарт Unicode на домашней странице Unicode.
Однако то, как вы можете на самом деле этого добиться, зависит от фреймворка / ОС /... над которым вы работаете. Если вы используете.NET, вы можете использовать метод String.Normalize, принимающий перечисление System.Text.NormalizationForm.
Самым простым способом (для меня) было бы просто поддерживать массив разреженных отображений, который просто изменяет ваши кодовые точки 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
).
Это не зависящий от языка ответ, поэтому, если вы имеете в виду конкретный язык, найдутся лучшие способы (хотя в любом случае они все, вероятно, дойдут до этого на самых низких уровнях).
Что следует учитывать: если вы идете по пути получения одного "перевода" каждого слова, вы можете упустить некоторые возможные альтернативы.
Например, на немецком языке при замене "s-set" некоторые люди могут использовать "B", в то время как другие могут использовать "ss". Или, заменяя умноженный o на "o" или "oe". Любое решение, которое вы придумали, в идеале, я думаю, должно включать и то, и другое.
Для дальнейшего использования, вот метод расширения 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");
}