Я очень близко подошел к этому алгоритму с icunicode gem .
require 'icunicode'
def database_sort_key(key)
key.gsub(/\s+/,'').unicode_sort_key
end
array.sort_by { |v|
[database_sort_key(v), v.unicode_sort_key]
}
Сначала мы сортируем, используя ключ сортировки в Юникоде с удаленными пробелами.Затем, если они совпадают, мы сортируем по ключу сортировки в юникоде исходного значения.
Это работает вокруг слабости в unicode_sort_key
: пробелы не считаются слабыми.
2.4.4 :007 > "fo p".unicode_sort_key.bytes.map { |b| b.to_s(16) }
=> ["33", "45", "4", "47", "1", "8", "1", "8"]
2.4.4 :008 > "foo".unicode_sort_key.bytes.map { |b| b.to_s(16) }
=> ["33", "45", "45", "1", "7", "1", "7"]
Обратите внимание, что пробел в fo p
так же важен, как и любой другой символ.Это приводит к 'fo p' < 'foo'
, что неверно.Мы работаем над этим, сначала удаляя пробелы перед генерацией ключа.
2.4.4 :011 > "fo p".gsub(/\s+/, '').unicode_sort_key.bytes.map { |b| b.to_s(16) }
=> ["33", "45", "47", "1", "7", "1", "7"]
2.4.4 :012 > "foo".gsub(/\s+/, '').unicode_sort_key.bytes.map { |b| b.to_s(16) }
=> ["33", "45", "45", "1", "7", "1", "7"]
Теперь 'foo' < 'fo p'
, что правильно.
Но из-за нормализации у нас могут быть значения, которые выглядят както же самое после удаления пробела, fo o
должно быть меньше foo
.Поэтому, если database_sort_key
s одинаковы, мы сравниваем их простые unicode_sort_key
s.
. Есть несколько крайних случаев, когда это неправильно.foo
должно быть меньше fo o
, но это возвращает его назад.
Вот как Enumerable
методов.
module Enumerable
# Just like `sort`, but tries to sort the same as the database does
# using the proper Unicode collation algorithm. It's close.
#
# Differences in spacing, cases, and accents are less important than
# character differences.
#
# "foo" < "fo p" o vs p is more important than the space difference
# "Foo" < "fop" o vs p is more important than is case difference
# "föo" < "fop" o vs p is more important than the accent difference
#
# It does not take a block.
def sort_like_database(&block)
if block_given?
raise ArgumentError, "Does not accept a block"
else
# Sort by the database sort key. Two different strings can have the
# same keys, if so sort just by its unicode sort key.
sort_by { |v| [database_sort_key(v), v.unicode_sort_key] }
end
end
# Just like `sort_by`, but it sorts like `sort_like_database`.
def sort_by_like_database(&block)
sort_by { |v|
field = block.call(v)
[database_sort_key(field), field.unicode_sort_key]
}
end
# Sort by the unicode sort key after stripping out all spaces. This provides
# a decent simulation of the Unicode collation algorithm and how it handles
# spaces.
private def database_sort_key(key)
key.gsub(/\s+/,'').unicode_sort_key
end
end