ЗмістНатисність на посилання, щоб перейти до потрібного місця
Сортування тексту здається простою задачею — поки не натрапляєш на український алфавіт у Ruby.
["ґ", "г", "є", "е", "і", "и", "ї", "й"].sort => ["г", "е", "и", "й", "є", "і", "ї", "ґ"]
Як бачимо, "ґ" потрапила в самий кінець. Але в українській абетці має бути навпаки: "ґ" стоїть після "г".
Unicode ≠ Алфавіт
Ruby сортує рядки за замовчуванням за Unicode codepoints, тобто за технічним порядком символів у таблиці Unicode, а не за граматичним порядком літер в українській мові.
uk_alphabet = "АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ" puts "Великі літери:" uk_alphabet.each_char do |char| dec = char.ord hex = dec.to_s(16).upcase.rjust(4, '0') puts "#{char} -> dec: #{dec}, hex: U+#{hex}" end puts "\nМалі літери:" uk_alphabet.downcase.each_char do |char| dec = char.ord hex = dec.to_s(16).upcase.rjust(4, '0') puts "#{char} -> dec: #{dec}, hex: U+#{hex}" end
Великі літери: А -> dec: 1040, hex: U+0410 Б -> dec: 1041, hex: U+0411 В -> dec: 1042, hex: U+0412 Г -> dec: 1043, hex: U+0413 Ґ -> dec: 1168, hex: U+0490 Д -> dec: 1044, hex: U+0414 Е -> dec: 1045, hex: U+0415 Є -> dec: 1028, hex: U+0404 Ж -> dec: 1046, hex: U+0416 З -> dec: 1047, hex: U+0417 И -> dec: 1048, hex: U+0418 І -> dec: 1030, hex: U+0406 Ї -> dec: 1031, hex: U+0407 Й -> dec: 1049, hex: U+0419 К -> dec: 1050, hex: U+041A Л -> dec: 1051, hex: U+041B М -> dec: 1052, hex: U+041C Н -> dec: 1053, hex: U+041D О -> dec: 1054, hex: U+041E П -> dec: 1055, hex: U+041F Р -> dec: 1056, hex: U+0420 С -> dec: 1057, hex: U+0421 Т -> dec: 1058, hex: U+0422 У -> dec: 1059, hex: U+0423 Ф -> dec: 1060, hex: U+0424 Х -> dec: 1061, hex: U+0425 Ц -> dec: 1062, hex: U+0426 Ч -> dec: 1063, hex: U+0427 Ш -> dec: 1064, hex: U+0428 Щ -> dec: 1065, hex: U+0429 Ь -> dec: 1068, hex: U+042C Ю -> dec: 1070, hex: U+042E Я -> dec: 1071, hex: U+042F Малі літери: а -> dec: 1072, hex: U+0430 б -> dec: 1073, hex: U+0431 в -> dec: 1074, hex: U+0432 г -> dec: 1075, hex: U+0433 ґ -> dec: 1169, hex: U+0491 д -> dec: 1076, hex: U+0434 е -> dec: 1077, hex: U+0435 є -> dec: 1108, hex: U+0454 ж -> dec: 1078, hex: U+0436 з -> dec: 1079, hex: U+0437 и -> dec: 1080, hex: U+0438 і -> dec: 1110, hex: U+0456 ї -> dec: 1111, hex: U+0457 й -> dec: 1081, hex: U+0439 к -> dec: 1082, hex: U+043A л -> dec: 1083, hex: U+043B м -> dec: 1084, hex: U+043C н -> dec: 1085, hex: U+043D о -> dec: 1086, hex: U+043E п -> dec: 1087, hex: U+043F р -> dec: 1088, hex: U+0440 с -> dec: 1089, hex: U+0441 т -> dec: 1090, hex: U+0442 у -> dec: 1091, hex: U+0443 ф -> dec: 1092, hex: U+0444 х -> dec: 1093, hex: U+0445 ц -> dec: 1094, hex: U+0446 ч -> dec: 1095, hex: U+0447 ш -> dec: 1096, hex: U+0448 щ -> dec: 1097, hex: U+0449 ь -> dec: 1100, hex: U+044C ю -> dec: 1102, hex: U+044E я -> dec: 1103, hex: U+044F
Наслідки Unicode порядку
Через те, що Unicode-коди розташовані не в алфавітному порядку, стандартне сортування Ruby видає неправильний порядок для українських слів, особливо для літер "ґ", "є", "і", "ї".
Моє рішення: ukrainian_sort - Ruby-gem для правильного сортування українських слів
Щоб уникнути цієї проблеми в своїх особистих проєктах, я створив бібліотеку ukrainian_sort, яка реалізує сортування за офіційним українським алфавітом. Робив для використання в своїх сайд-проєктах.
Вона порівнює слова літеру за літерою, використовуючи власний порядок:["г", "ґ", "е", "є", "и", "і", "ї", "й"]
require 'ukrainian_sort' words = ["ґава", "груша", "єнот", "яблуко"] sorted_words = UkrainianSort.sort(words) puts sorted_words # => ["груша", "ґава", "єнот", "яблуко"]
А так сортує Ruby "з коробки":
words.sort => ["груша", "яблуко", "єнот", "ґава"]
Чому в англійській все працює "нормально"?
Англійський алфавіт — це простий набір латинських літер від A до Z у безперервному діапазоні Unicode (U+0041–U+005A для великих і U+0061–U+007A для малих літер). Це означає, що порядок символів у Unicode збігається з алфавітним порядком англійської мови.
Тому стандартне сортування Ruby (Array#sort або String#<=>), яке порівнює символи за їх Unicode кодами, працює коректно для англійських слів.
В яких ще мовах існує проблема сортування?
Відповідь проста - у багатьох. Багато інших мов мають складніші алфавіти, де порядок літер у Unicode не відповідає лінгвістичному порядку, наприклад:
- Німецька — де ä, ö, ü, ß мають особливий порядок;
- Шведська — додає літери å, ä, ö після z;
- Чеська, словацька, польська — мають діакритичні літери, які за Unicode йдуть не послідовно;
- Французька, іспанська, португальська — різні правила сортування з апострофами, тильдами, акцентами.
Складність питання
Unicode задає універсальний номер символу, але не визначає лінгвістичний порядок сортування. Для коректного сортування мов часто потрібні спеціальні правила — collation rules, які враховують:
- Порядок літер,
- Особливі символи,
- Акценти
- Чи вважати великі і малі літери однаковими,
- Лігатури та інші мовні особливості.
Як це зазвичай вирішують?
- Через локалізовані сортувальники (наприклад, ICU — International Components for Unicode). Але якогось рішення для себе я швидко знайти не зміг.
- Спеціальні бібліотеки для сортування (gem’и, пакети).
- Ручне визначення порядку для конкретної мови.
Для англійської мови Unicode-послідовність символів збігається з алфавітним порядком, тож sort працює без проблем. Але для більшості інших мов треба додатково враховувати лінгвістичні правила сортування, бо Unicode — це лише кодова таблиця, а не алгоритм сортування.
ukrainian_sort (GitHub / RubyGems) - один з перших gem'ів які я викатив публічно. Зазвичай роблю приватні репозиторії з рішеннями під себе. Тож можете сміливо робити Issues та Pull-реквести. Скоріш за все там є ще над чим поправцювати.
Цей допис поки що не має жодних доповнень від автора/ки.