Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование на языке Ruby.docx
Скачиваний:
19
Добавлен:
06.09.2019
Размер:
1.74 Mб
Скачать

4.2.5. Упорядочение строк

Обычно, хотя и не всегда, строки упорядочиваются по алфавиту или сходным образом. Упорядочение тесно связано с нормализацией: в обоих случаях применяются одни и те же идеи и библиотеки.

Предположим, например, что мы хотим отсортировать такой массив строк:

eacute = [0x00Е9].pack('U')

acute = [0x0301].pack('U')

array = ["epicurian", "#{eacute}p#{eacute}e", "e#{acute}lan"]

# ["epicurian", "éрéе", "élan"]

Что произойдет, если передать этот массив методу Array#sort?

array.sort # ["epicurian", "élan", "éрéе"]

He годится!.. Попытаемся понять, почему так получилось. Сортируемые строки Ruby сравнивает побайтно. Чтобы убедиться в этом, достаточно взглянуть на первые несколько байтов каждой строки:

array.map {|item| "#{item}: #{item.unpack('С*')[0,3].join(',')}" }

# ["epicurian: 101,112,105", "éрéе: 195,169,112",

# "élan: 101,204,129"]

Тут возникают две трудности. Во-первых, символы UTF-8, не имеющие аналога в кодировке ASCII, начинаются с байта, имеющего большое числовое значение, а стало быть, после сортировки неизбежно окажутся после ASCII-символов. Во-вторых, составные латинские символы оказываются раньше монолитных из-за первого ASCII-байта.

В системные библиотеки обычно включают функции сортировки, которые сравнивают строки в соответствии с правилами конкретного языка. В библиотеке, поставляемой вместе с компилятором языка С, для этого служат функции strxfrm и strcoll.

Имейте в виду, что проблема возникает даже в случае кодировки ASCII. При сортировке ASCII-строк в Ruby производится прямое лексикографическое сравнение, однако в реальной жизни (например, если мы хотим отсортировать по названиям книги из библиотеки Конгресса США) есть много правил, которые не учитываются при таком упрощенном подходе.

Для упорядочения строк можно создать промежуточные строки и отсортировать именно их. Как конкретно это сделать, зависит от предъявляемых требований и языка; универсального алгоритма не существует.

Предположим, что список обрабатывается согласно правилам английского языка, причем диакритические знаки игнорируются. Первым делом нужно определить методику трансформации. Мы приведем все символы к составному виду, а затем исключим диакритические знаки, оставив только базовые символы. Для модифицирующих диакритических знаков в Unicode выделен диапазон отU+0300 to U+036F:

def transform(str)

 Unicode.normalize_KD(str).unpack('U*').select{ |cp|

  cp < 0x0300 || cp > 0x036F

 }.pack('U*')

end

array.map{|x| transform(x) } # ["epicurian", "epee", "elan"]

Затем создадим хэшированную таблицу, чтобы установить соответствие между исходными и трансформированными строками, и воспользуемся ей для сортировки исходных строк. Наличие такой таблицы позволяет провести трансформацию только один раз.

def collate(array)

 transformations = array.inject({}) do |hash, item|

  hash[item] = yield item

  hash

 end

 array.sort_by {|x| transformations[x] }

end

collate(array) {|a| transform(a) } # ["élan", "épée", "epicurian"]

Уже лучше, но мы еще не учли прописные буквы и эквивалентность символов. Возьмем для примера немецкий язык.

На самом деле в немецком языке есть несколько способов упорядочения; мы остановимся на стандарте DIN-2 (как в телефонном справочнике). Согласно этому стандарту, символ ß (эсцет) эквивалентен ss, а умляут эквивалентен букве е (то есть ö — то же самое, что ое и т.д.).

Наш метод трансформации должен учитывать эти детали. Снова начнем с декомпозиции составных символов. Например, модифицирующая трема (умляут) представляется кодовой позициейU+0308. За основу мы возьмем метод преобразования регистра, имеющийся в Ruby, но несколько дополним его. Вот как выглядит теперь код трансформации:

def transform_de(str)

 decomposed = Unicode.normalize_KD(str).downcase

 decomposed.gsub!('ß', 'ss')

 decomposed.gsub([0x0308].pack('U'), 'e')

end

array = ["Straße", "öffnen"]

array.map {|x| transform_de(x) } # ["strasse", "oeffnen"]

He для всех языков годится такой прямолинейный подход. Например, в испанском между буквами n и о есть еще буква ñ. Однако, если каким-то образом сдвинуть оставшиеся буквы, то мы справимся и с этой проблемой. В листинге 4.1 для упрощения обработки нормализация применена к монолитным символам. Кроме того, мы облегчили себе жизнь, игнорируя различия между буквами с диакритическими знаками и без них.