Cała oryginalna treść jest tworzona po ukraińsku. Nie wszystkie treści zostały jeszcze przetłumaczone. Niektóre posty mogą być dostępne tylko po ukraińsku.Dowiedz się więcej
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.