Проблема с алфавитной сортировкой строк в Android и буквой Ё
Задача: есть список имён - нужно разбить на группы по первой букве: А, Б, В, Г, Д, Е, Ё, Ж, ...
Я попробовал String.compareTo и java.text.Collator но не нашел работающего варианта (чтобы сортировал строго по порядку букв в русском алфавите)
String.compareTo - сравнивает не по порядку букв в алфавите (несмотря на то что в описании указан lexicographic ordering)
val list = listOf("аа", "яя", "жж", "еж", "ёж", "ежовый")
val sorted = list.sortedWith(Comparator { a, b -> a.compareTo(b, ignoreCase = true) })
println(sorted.toString())
result: [аа, еж, ежовый, жж, яя, ёж]
java.text.Collator - не нашел варианта при котором Ё не смешивалась бы с Е
val list = listOf("аа", "яя", "жж", "еж", "ёж", "ежовый")
val collator = java.text.Collator.getInstance().apply {
decomposition = Collator.NO_DECOMPOSITION
strength = Collator.TERTIARY
}
val sorted = list.sortedWith(Comparator { a, b -> collator.compare(a, b) })
println(sorted.toString())
result: [аа, еж, ёж, ежовый, жж, яя]
Правильно ли я понимаю что в Android нет сортировки по алфавиту и нужно сочинять свою ?
Ответы (3 шт):
Как костыльный вариант могу предложить такой способ:
val l = listOf("аа", "яя", "жж", "еж", "ёж", "ежовый")
val sorted = l.sortedWith(Comparator { a, b -> a.compareTo(b, ignoreCase = true) }) //сортируем стандартно
val indexE = sorted.indexOfFirst { it.toLowerCase().firstOrNull() == 'ё' }
val result = sorted.subList(0, indexE).toMutableList() //получаем отсортированный список слов, начинающихся НЕ с буквы Ё
val eList = sorted.subList(indexE, sorted.size) //получаем отсортированный список слов, начинающихся с Ё
val insertPos = sorted.indexOfLast { it.toLowerCase().firstOrNull() == 'е' }
result.addAll(insertPos + 1, eList) //вставляем слова, начинающиеся с Ё за словами, начинающимися с Е
println(result.toString())
Результат вроде правильный, но, понятно, хреново выглядит))
Сортировка будет корректной только для первых букв, вторые и прочие - ё будет считаться самой последней буквой алфавита.
Пока не придумал ничего лучше чем изменить String.compareToIgnoreCase таким образом:
val CASE_INSENSITIVE_ORDER_RU = kotlin.Comparator { s1: String, s2: String ->
val n1 = s1.length
val n2 = s2.length
val min = Math.min(n1, n2)
for (i in 0 until min) {
var c1 = s1[i]
var c2 = s2[i]
if (c1 != c2) {
c1 = Character.toUpperCase(c1)
c2 = Character.toUpperCase(c2)
if (c1 != c2) {
c1 = Character.toLowerCase(c1)
c2 = Character.toLowerCase(c2)
if (c1 != c2) {
// No overflow because of numeric promotion
return@Comparator when {
c1 == 'ё' ->
if (c2 == 'е') 1 else 'е' - c2
c2 == 'ё' ->
if (c1 == 'е') -1 else c1 - 'е'
else ->
c1 - c2
}
}
}
}
}
return@Comparator n1 - n2
}
val list = listOf("аа", "яя", "жж", "еж", "ёж", "ежовый",
"Аа", "Яя", "Жж", "ЕЖ", "Ёж", "Ежовый")
val sorted = list.sortedWith(CASE_INSENSITIVE_ORDER_RU)
result: [аа, Аа, еж, ЕЖ, ежовый, Ежовый, ёж, Ёж, жж, Жж, яя, Яя]
Update:
решение от @barmaley-red-star на основе java.text.RuleBasedCollator
val list = listOf("0", "9", "1",
"аа", "яя", "жж", "еж", "ёж", "ежовый",
"Аа", "Яя", "Жж", "Еж", "Ёж", "Ежовый",
"aa", "bb", "cc")
val yoRule = "& а < б < в < г < д < е < ё < ж < з < и < й "+
"< к < л < м < н < о < п < р < с < т < у < ф "+
"< х < ц < ч < ш < щ < ъ < ы < ь < э < ю < я"
val ruleBasedCollator = java.text.RuleBasedCollator(yoRule)
val locale = Locale("ru")
val sorted = list.sortedWith(Comparator { a, b ->
ruleBasedCollator.compare(a.toLowerCase(locale), b.toLowerCase(locale))
})
println("RuleBasedCollator -> $sorted")
RuleBasedCollator -> [0, 1, 9, aa, bb, cc, аа, Аа, еж, Еж, ежовый, Ежовый, ёж, Ёж, жж, Жж, яя, Яя]
Коллеги,
Не там по-моему ищете:
1) Android здесь не причем, корень в Java конечно же
2) Надо все же таки явно указывать на локаль, иначе будет взята локаль по умолчанию, которая может быть какой угодно:
Locale locale = Locale.RU;
Collator collator = Collator.getInstance(locale);
3) Чтобы различать е и ё надо задавать флаг коллатора как SECONDARY согласно мануалу:
Collator strength value. When set, only SECONDARY and above differences are considered significant during comparison. The assignment of strengths to language features is locale dependant. A common example is for different accented forms of the same base letter ("a" vs "ä") to be considered a SECONDARY difference.
И только потом уже запускать компарацию.
3) Если уж совсем неймётся, то можно применить RuleBasedCollator для ё, и обойтись без велосипедов:
String yoRule = "< е < ё";
RuleBasedCollator ruleBasedCollator =
new RuleBasedCollator(yoRule);
И напоследок, не надо думать, что наш русский язык какой-то супер особенный, есть языки и позаковыристее, в комитете Unicode отнюдь не дураки сидят и давно уже разработали корректный алгоритм сравнения, на основе которой и работает Collation в Java
Update
val list = listOf("аа", "яя", "жж", "еж", "ежп", "ёж", "ёжя", "ежовый")
val yoRule = "< а < б < в < г < д < е < ё < ж < з < и < й < к < л < м < н < о < п < р < с < т < у < ф < х < ц < ч < ш < щ < ъ < ы < ь < я < ю < я"
val ruleBasedCollator = RuleBasedCollator(yoRule)
val sorted = list.sortedWith(Comparator { a, b -> ruleBasedCollator.compare(a, b) })
println(sorted.toString())
[аа, еж, ежовый, ежп, ёж, ёжя, жж, яя]
Так работает?