Думал, что после стольких лет Python уже не удивит меня, однако пословица Век живи, век учись стала для меня как никогда актуальной вчера. Задача была весьма простая: отсортировать список с строками, где строка - это украинское имя, то есть вроде бы все должно быть предельно просто используя Python 3.7.2:
data = ['Андрій', 'Ігор', 'Євген', 'Віталій']
assert sorted(data) == ['Андрій', 'Віталій', 'Євген', 'Ігор']
Но нет, not so fast!
$ python
Python 3.7.2 (default, Jan 4 2019, 12:23:06)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> data = ['Андрій', 'Ігор', 'Євген', 'Віталій']
>>> sorted(data)
['Євген', 'Ігор', 'Андрій', 'Віталій']
Интересно. Значит что-то не так с локалью. Нужно установить правильную локаль и попробовать еще раз.
$ python
Python 3.7.2 (default, Jan 4 2019, 12:23:06)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'uk_UA.UTF-8')
'uk_UA.UTF-8'
>>> locale.setlocale(locale.LC_COLLATE, 'uk_UA.UTF-8')
'uk_UA.UTF-8'
>>> data = ['Андрій', 'Ігор', 'Євген', 'Віталій']
>>> sorted(data)
['Євген', 'Ігор', 'Андрій', 'Віталій']
Хм. Странно. Значит нужно пойти почитать доку locale и найти там функцию, которая будет делать locale compare. Точно же есть такая.
$ python
Python 3.7.2 (default, Jan 4 2019, 12:23:06)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'uk_UA.UTF-8')
'uk_UA.UTF-8'
>>> locale.setlocale(locale.LC_COLLATE, 'uk_UA.UTF-8')
'uk_UA.UTF-8'
>>> data = ['Андрій', 'Ігор', 'Євген', 'Віталій']
>>> sorted(data, key=locale.strxfrm)
['Ігор', 'Євген', 'Андрій', 'Віталій']
>>> import functools
>>> sorted(data, key=functools.cmp_to_key(locale.strcoll))
['Ігор', 'Євген', 'Андрій', 'Віталій']
Та ладно. Не верю. Все должно работать. Аргх.
После того, как эмоции уляглись, вспоминаю про ICU и думаю, ну ок, точно есть биндинги ICU для Python и там должна быть функция, которую можно будет скормить в key
для правильной сортировки значений в списке.
-
Устанавливаем ICU в систему,
-
Для macOS:
$ brew install icu4c $ export PATH="/usr/local/opt/icu4c/bin:$PATH"
-
Для Ubuntu Linux:
# apt install libicu-dev icu-devtools
-
Для macOS:
-
Устанавливаем PyICU,
или по старинке:$ poetry add PyICU
$ pip install PyICU
$ python
Python 3.7.2 (default, Jan 4 2019, 12:23:06)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import icu
>>> collator = icu.Collator.createInstance(icu.Locale('uk_UA.UTF-8'))
>>> data = ['Андрій', 'Ігор', 'Євген', 'Віталій']
>>> sorted(data, key=collator.getSortKey)
['Андрій', 'Віталій', 'Євген', 'Ігор']
Фух! Работает! Так что да, чистая правда: век живи, век учись!
ps. В комментариях @xnull
поделился еще одним способом сортировки при помощи pyuca библиотеки, которая не требует установки в систему никаких дополнительных зависимостей.
$ poetry add pyuca # pip install pyuca
$ python
Python 3.7.2 (default, Jan 2 2019, 13:30:18)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyuca
>>> coll = pyuca.Collator()
>>> data = ['Євген', 'Ігор', 'Андрій', 'Віталій']
>>> sorted(data, key=coll.sort_key)
['Андрій', 'Віталій', 'Євген', 'Ігор']