Сортировка строк UTF-8 в RoR

Я пытаюсь выяснить "правильный" способ сортировки строк UTF-8 в Ruby on Rails.

В моем приложении у меня есть поле выбора, которое заполняется странами. Поскольку мое приложение локализовано, каждый существующий язык имеет файл country.yml, который связывает идентификатор страны с локализованным именем для этой страны. Я не могу сортировать строки вручную в файле yml, потому что мне нужно, чтобы ID был согласован во всех локалях.

Что я сделал, создайте метод ascii_name, который использует unidecode для преобразования акцентированных и нелатинских символов в свои ascii эквивалент (например, "Afeganistão" станет "Afeganistao" ), а затем сортировать по этому:

require 'unidecode'

class Country
  def ascii_name
    Unidecoder.decode(name).gsub("[?]", "").gsub(/`/, "'").strip
  end
end

Country.all.sort_by(:&ascii_name)

Однако есть очевидные проблемы с этим:

  • Невозможно правильно отсортировать нелатинские локали, так как не может быть прямого аналогичного латинского символа.
  • Он не делает различия между буквой и всеми акцентированными формами этой буквы (так, например, A и Ä становятся взаимозаменяемыми)

Кто-нибудь знает, как лучше я могу сортировать строки?

Ответы

Ответ 2

Сравнение строк в Ruby, основанных на байтовых значениях символов:

%w[à a e].sort
# => ["a", "e", "à"]

Чтобы правильно сортировать строки в соответствии с локалью, можно использовать ffi-icu драгоценный камень:

require "ffi-icu"

ICU::Collation.collate("it_IT", %w[à a e])
# => ["a", "à", "e"]

ICU::Collation.collate("de", %w[a s x ß])
# => ["a", "s", "ß", "x"]

В качестве альтернативы:

collator = ICU::Collation::Collator.new("it_IT")
%w[à a e].sort { |a, b| collator.compare(a, b) }
# => %w[a à e]

Обновить. Чтобы проверить, как строки должны сортироваться в соответствии с языковыми правилами, проект ICU предоставляет этот хороший инструмент.

Ответ 3

Единственным решением, которое я нашел до сих пор, является использование ActiveSupport::Inflector.transliterate(string) для замены символов Unicode на ASCII и сортировки:

Country.all.sort_by do |country|
  ActiveSupport::Inflector.transliterate country.name
end

Теперь единственная проблема заключается в том, что это выравнивает "ä" с "a" (DIN 5007-1), и я заканчиваю "Ägypten" перед "Albanien", в то время как я ожидаю, что это будет наоборот. К счастью, транслитерация настраивается как заменить символы.

См. документацию: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate

Ответ 4

Есть несколько способов пойти. Вы можете конвертировать строки UTF в шестнадцатеричные строки, а затем сортировать их:

s.split(//).collect { |x| x.unpack('U').to_s }.join

или вы можете использовать библиотеку iconv. Прочитайте его и используйте по мере необходимости (от dzone):

#add this to environment.rb
#call to_iso on any UTF8 string to get a ISO string back
#example : "Cédez le passage aux français".to_iso

class String
  require 'iconv' #this line is not needed in rails !
  def to_iso
    Iconv.conv('ISO-8859-1', 'utf-8', self)
  end
end

Ответ 5

Единственное работающее решение, которое я нашел до сих пор (по крайней мере, для Ruby 1.8, потому что Ruby 1.9 лучше обрабатывать Unicode) Unicode от Yoshida Masato, Здесь вы можете найти метод Unicode.strcmp.

РЕДАКТИРОВАТЬ: Извините, это решение также использует разложение NFD со всеми его ограничениями.

Ответ 6

То, что вы пытаетесь сделать, - очень грязное предложение. Нет никакого способа сделать прозрачную транслитерацию для всех символов Юникода, потому что значение орграфов изменяется от локали к языку, а строки могут расти ОГРОМНЫМ (если вы хотите заменить 10 китайских символов их фонетическими эквивалентами). Не ходите туда.

Почему вы хотите транслитерировать имена в первую очередь? Для URL-адресов? Браузеры обрабатывают URL-адреса Unicode прилично сейчас, поэтому вы изобретаете огромную проблему из воздуха. Если вам нужны идентификаторы, предварительно подготовьте свои списки, чтобы включить стабильный числовой идентификатор для каждой страны и использовать его в качестве идентификатора. Или сохраните английское имя страны в качестве идентификатора (вы можете бесплатно загрузить списки стран, в которых существует справочная система ISO).

Если вам действительно нужна хорошая транслитерация для Unicode (и в этом случае это не то, что вам нужно), см. библиотеки IBM ICU, для них есть дремлющий камень.

Ответ 7

Вы пытались получить доступ к методу mb_chars для каждой из строк вашей страны? mb_chars - это прокси-сервер, который добавляет ActiveSupport, который определяет безопасные версии Unicode всех методов String. Если компаратор поддерживает Unicode, сортировка должна работать правильно.