Spis treściKliknij link, aby przejść do wybranego miejsca
Ta treść została automatycznie przetłumaczona z ukraińskiego.
Sortowanie tekstu wydaje się prostym zadaniem — dopóki nie natrafisz na ukraiński alfabet w Ruby.
["ґ", "г", "є", "е", "і", "и", "ї", "й"].sort => ["г", "е", "и", "й", "є", "і", "ї", "ґ"]
Jak widać, "ґ" znalazła się na końcu. Ale w ukraińskim alfabecie powinno być odwrotnie: "ґ" stoi po "г".
Unicode ≠ Alfabet
Ruby sortuje ciągi domyślnie według Unicode codepoints, czyli według technicznego porządku znaków w tabeli Unicode, a nie według gramatycznego porządku liter w języku ukraińskim.
uk_alphabet = "АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ"
puts "Wielkie litery:"
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 "\nMałe litery:"
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
Wielkie litery: А -> 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 Małe litery: а -> 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
Skutki porządku Unicode
Ze względu na to, że kody Unicode nie są uporządkowane alfabetycznie, standardowe sortowanie Ruby daje błędny porządek dla ukraińskich słów, szczególnie dla liter "ґ", "є", "і", "ї".
Moje rozwiązanie: ukrainian_sort - Ruby-gem do poprawnego sortowania ukraińskich słów
Aby uniknąć tego problemu w swoich osobistych projektach, stworzyłem bibliotekę ukrainian_sort, która implementuje sortowanie według oficjalnego ukraińskiego alfabetu. Zrobiłem to do użycia w swoich projektach pobocznych.
Porównuje słowa litera po literze, używając własnego porządku:["г", "ґ", "е", "є", "и", "і", "ї", "й"]
require 'ukrainian_sort' words = ["ґава", "груша", "єнот", "яблуко"] sorted_words = UkrainianSort.sort(words) puts sorted_words # => ["груша", "ґава", "єнот", "яблуко"]
A tak sortuje Ruby "z pudełka":
words.sort => ["груша", "яблуко", "єнот", "ґава"]
Czemu w angielskim wszystko działa "normalnie"?
Angielski alfabet to prosty zestaw liter łacińskich od A do Z w ciągłym zakresie Unicode (U+0041–U+005A dla wielkich i U+0061–U+007A dla małych liter). Oznacza to, że porządek znaków w Unicode pokrywa się z alfabetycznym porządkiem języka angielskiego.
Dlatego standardowe sortowanie Ruby (Array#sort lub String#<=>), które porównuje znaki według ich kodów Unicode, działa poprawnie dla angielskich słów.
W jakich innych językach występuje problem sortowania?
Odpowiedź jest prosta - w wielu. Wiele innych języków ma bardziej skomplikowane alfabety, gdzie porządek liter w Unicode nie odpowiada porządkowi lingwistycznemu, na przykład:
- Niemiecki — gdzie ä, ö, ü, ß mają szczególny porządek;
- Szwedzki — dodaje litery å, ä, ö po z;
- Czeski, słowacki, polski — mają litery diakrytyczne, które w Unicode nie są uporządkowane;
- Francuski, hiszpański, portugalski — różne zasady sortowania z apostrofami, tyldami, akcentami.
Trudność problemu
Unicode ustala uniwersalny numer znaku, ale nie definiuje lingwistycznego porządku sortowania. Aby poprawnie sortować języki, często potrzebne są specjalne zasady — zasady porządkowania, które uwzględniają:
- Porządek liter,
- Szczególne znaki,
- Akcenty
- Czy traktować wielkie i małe litery jako równe,
- Ligatury i inne cechy językowe.
Jak to zazwyczaj rozwiązują?
- Poprzez lokalizowane sortery (na przykład, ICU — International Components for Unicode). Ale jakiegoś rozwiązania dla siebie szybko nie znalazłem.
- Specjalne biblioteki do sortowania (gem’y, pakiety).
- Ręczne określenie porządku dla konkretnego języka.
Dla języka angielskiego sekwencja znaków Unicode pokrywa się z alfabetycznym porządkiem, więc sort działa bez problemów. Ale dla większości innych języków trzeba dodatkowo uwzględnić zasady lingwistyczne sortowania, ponieważ Unicode to tylko tabela kodów, a nie algorytm sortowania.
ukrainian_sort (GitHub / RubyGems) - jeden z pierwszych gem'ów, które opublikowałem publicznie. Zazwyczaj tworzę prywatne repozytoria z rozwiązaniami dla siebie. Więc możesz śmiało zgłaszać Issues i Pull-requests. Najprawdopodobniej jest tam jeszcze nad czym pracować.
Ten post nie ma jeszcze żadnych dodatków od autora.