Como classifico strings Unicode em ordem alfabética em Python?

97

Python classifica por valor de byte por padrão, o que significa que é vem depois de z e outras coisas igualmente engraçadas. Qual é a melhor maneira de classificar alfabeticamente em Python?

Existe uma biblioteca para isso? Não consegui encontrar nada. De preferência, a classificação deve ter suporte de idioma para que entenda que åäö deve ser classificado após z em sueco, mas que ü deve ser classificado por u etc. O suporte Unicode é, portanto, praticamente um requisito.

Se não houver biblioteca para isso, qual é a melhor maneira de fazer isso? Basta fazer um mapeamento de letra para um valor inteiro e mapear a string para uma lista de inteiros com isso?

Lennart Regebro
fonte
11
Observe que isso depende ainda mais do local: em sueco (como você afirma), "Ä" vem depois de "Z", mas em alemão, "Ä" geralmente é classificado como "AE".
balpha
@Georg: Houve algum motivo para você abrir uma recompensa por isso? A locale.strcollresposta está correta quando você precisa da classificação Unicode usando a localidade do usuário, e o ICU responde o que você deseja quando você precisa de mais do que isso (agrupamento usando mais de uma localidade). Na maioria das vezes, você quer locale.strcoll.
Glenn Maynard de
@Glenn: Eu queria saber como locale.strcollfunciona e principalmente o que o ICU faz melhor do que a função Python. Basicamente, mais atenção para a pergunta.
Georg Schölly de
1
@Georg: Ultimamente, tenho brincado muito com o algoritmo de agrupamento Unicode, como você pode ver na minha resposta. É realmente excelente poder, por exemplo, ordenar --locale=de__phonebookquando você precisa. O módulo Perl passa no conjunto de testes do UCA, e o script que forneci torna muito mais fácil jogar com todo o UCA, além de todas as suas opções, incluindo locais, apenas a partir da linha de comando. Pode não responder à pergunta, mas ainda assim deve ser muito interessante. Se você estiver na Suíça, tenho certeza de que precisa de flexibilidade. :)
tchrist

Respostas:

75

A biblioteca ICU da IBM faz isso (e muito mais). Possui ligações Python: PyICU .

Atualização : A principal diferença na classificação entre ICU e locale.strcollé que ICU usa o algoritmo de agrupamento Unicode completo enquanto strcollusa ISO 14651 .

As diferenças entre esses dois algoritmos são resumidas brevemente aqui: http://unicode.org/faq/collation.html#13 . Esses são casos especiais bastante exóticos, que raramente deveriam importar na prática.

>>> import icu # pip install PyICU
>>> sorted(['a','b','c','ä'])
['a', 'b', 'c', 'ä']
>>> collator = icu.Collator.createInstance(icu.Locale('de_DE.UTF-8'))
>>> sorted(['a','b','c','ä'], key=collator.getSortKey)
['a', 'ä', 'b', 'c']
Rafał Dowgird
fonte
Isso funciona da mesma forma para Python 2 e Python 3? Usei locale.strxfrma resposta de u0b34a0f6ae e parece funcionar e é muito mais elegante e não requer nenhum software adicional.
sup
Não funciona com Python3 para mim, sudo pip3 install PyICUfalha na instalação e também para Python2.
imrek de
Tive que instalar libicu-devel.x86_64 para pyICU para compilar e instalar do Pip. Funciona, embora a saída do último comando 'classificado' seja: ['a', '\ xc3 \ xa4', 'b', 'c']
Mike Stoddart
53

Eu não vejo isso nas respostas. Meu aplicativo classifica de acordo com a localidade usando a biblioteca padrão do python. É muito fácil.

# python2.5 code below
# corpus is our unicode() strings collection as a list
corpus = [u"Art", u"Älg", u"Ved", u"Wasa"]

import locale
# this reads the environment and inits the right locale
locale.setlocale(locale.LC_ALL, "")
# alternatively, (but it's bad to hardcode)
# locale.setlocale(locale.LC_ALL, "sv_SE.UTF-8")

corpus.sort(cmp=locale.strcoll)

# in python2.x, locale.strxfrm is broken and does not work for unicode strings
# in python3.x however:
# corpus.sort(key=locale.strxfrm)

Pergunta para Lennart e outros respondentes: Alguém sabe 'local' ou não cabe a essa tarefa?

u0b34a0f6ae
fonte
A propósito 1) Não creio que locale.strxfrm esteja quebrado para `str 'codificado em UTF-8; Fiz o benchmarking por aplicação e concluí que usar cmp = strcoll em objetos Unicode é mais barato do que decodificar tudo para UTF-8 e usar key = strxfrm
u0b34a0f6ae
6
A propósito 2) O módulo locale funcionará apenas com seus locais gerados (para uma máquina Linux), não qualquer localidade arbitrária. "locale -a" dirá qual
u0b34a0f6ae
6
@Georg: Eu acredito que a localidade suporta apenas um mapeamento simples substring-> collating_element. Ele não lida com coisas como expansões (æ classificadas como "ae"), classificação de acentos franceses (letras classificadas da esquerda para a direita, mas acentos da direita para a esquerda), rearranjo e provavelmente mais alguns. Detalhes aqui (conjunto completo de recursos do UCA): unicode.org/reports/tr10 e aqui (agrupamento local): chm.tu-dresden.de/edv/manuals/aix/files/aixfiles/LC_COLLATE.htm
Rafał Dowgird
2
Para responder claramente à pergunta: Sim, é até a tarefa. Aparentemente, existem alguns casos especiais em que o algoritmo de agrupamento Unicode completo lida melhor, mas a menos que você já saiba disso, provavelmente não notará.
Lennart Regebro
1
O maior problema aqui é: você deve definir o local globalmente para todo o aplicativo. - Você não pode ter isso apenas para a comparação em mãos.
Robert Siemer,
9

Experimente o algoritmo de agrupamento Python Unicode de James Tauber . Pode não funcionar exatamente como você deseja, mas vale a pena dar uma olhada. Para mais informações sobre os problemas, veja esta postagem de Christopher Lenz.

Vinay Sajip
fonte
Isso pelo menos corrige o problema genérico. Eu acho que versões sensíveis ao idioma da lista de agrupamento também podem ser criadas.
Lennart Regebro
Isso não permite que você especifique a localidade e o arquivo de configuração de referência causa um ValueError.
thebjorn
8

Você também pode estar interessado em pyuca :

http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/

Embora certamente não seja a maneira mais exata, é uma maneira muito simples de pelo menos acertar um pouco. Ele também supera a localidade em um webapp, pois a localidade não é threadsafe e define as configurações de idioma em todo o processo. Também é mais fácil de configurar do que o PyICU, que depende de uma biblioteca C externa.

Eu carreguei o script para o github porque o original estava fora do ar no momento em que este livro foi escrito e tive que recorrer a caches da web para obtê-lo:

https://github.com/href/Python-Unicode-Collation-Algorithm

Eu usei com sucesso este script para classificar corretamente o texto Alemão / Francês / Italiano em um módulo do Plone.

href_
fonte
1 para pyuca. É bastante rápido (3 segundos para classificar 28.000 palavras), é puro Python e não requer dependência.
michaelmeyer de
7

Um resumo e uma resposta ampliada:

locale.strcollno Python 2, e de locale.strxfrmfato resolverá o problema e fará um bom trabalho, supondo que você tenha o local em questão instalado. Eu testei no Windows também, onde os nomes das localidades são confusamente diferentes, mas por outro lado, parece que todas as localidades com suporte estão instaladas por padrão.

ICUnão necessariamente faz isso melhor na prática, no entanto, faz muito mais . Mais notavelmente, possui suporte para divisores que podem dividir textos em diferentes idiomas em palavras. Isso é muito útil para idiomas que não possuem separadores de palavras. Você precisará ter um corpus de palavras para usar como base para a divisão, porque isso não está incluído, no entanto.

Ele também tem nomes longos para os locais para que você possa obter nomes de exibição bonitos para o local, suporte para outros calendários além do Gregoriano (embora eu não tenha certeza de que a interface Python suporte isso) e toneladas e toneladas de outros suportes de localidade mais ou menos obscuros .

Resumindo: se você deseja classificar em ordem alfabética e de acordo com a localidade, pode usar o localemódulo, a menos que tenha requisitos especiais ou também precise de mais funcionalidade dependente da localidade, como divisor de palavras.

Lennart Regebro
fonte
6

Vejo que as respostas já fizeram um excelente trabalho, só queria apontar uma ineficiência de codificação no Human Sort . Para aplicar uma tradução seletiva caractere por caractere a uma string unicode s, ele usa o código:

spec_dict = {'Å':'A', 'Ä':'A'}

def spec_order(s):
    return ''.join([spec_dict.get(ch, ch) for ch in s])

Python tem uma maneira muito melhor, mais rápida e mais concisa de realizar esta tarefa auxiliar (em strings Unicode - o método análogo para strings de byte tem uma especificação diferente e um pouco menos útil! -):

spec_dict = dict((ord(k), spec_dict[k]) for k in spec_dict)

def spec_order(s):
    return s.translate(spec_dict)

O dict que você passa para o translatemétodo tem ordinais Unicode (não strings) como chaves, e é por isso que precisamos dessa etapa de reconstrução do char-para-char original spec_dict. (Os valores no dicionário que você passa para traduzir [em oposição às chaves, que devem ser ordinais] podem ser ordinais Unicode, strings Unicode arbitrárias ou Nenhum para remover o caractere correspondente como parte da tradução, por isso é fácil especificar "ignorar um determinado caractere para fins de classificação "," mapear ä para ae para fins de classificação "e semelhantes).

No Python 3, você pode obter a etapa de "reconstrução" de forma mais simples, por exemplo:

spec_dict = ''.maketrans(spec_dict)

Consulte a documentação para outras maneiras de usar esse maketransmétodo estático no Python 3.

Alex Martelli
fonte
Este método é bom, mas não permite que você coloque á entre az e b
Barney
1

Ultimamente, tenho usado zope.ucol ( https://pypi.python.org/pypi/zope.ucol ) para esta tarefa. Por exemplo, classificando o alemão ß:

>>> import zope.ucol
>>> collator = zope.ucol.Collator("de-de")
>>> mylist = [u"a", u'x', u'\u00DF']
>>> print mylist
[u'a', u'x', u'\xdf']
>>> print sorted(mylist, key=collator.key)
[u'a', u'\xdf', u'x']

zope.ucol também envolve a UTI, então seria uma alternativa para a PyICU.

Brian Sutherland
fonte
1

Uma solução UCA completa

A maneira mais simples, fácil e direta de fazer isso é fazer um callout para o módulo da biblioteca Perl, Unicode :: Collate :: Locale , que é uma subclasse do módulo Unicode :: Collate padrão . Tudo o que você precisa fazer é passar ao construtor um valor de localidade "xv"para Suécia.

(Você pode não necessariamente apreciar isso para texto em sueco, mas como Perl usa caracteres abstratos, você pode usar qualquer ponto de código Unicode que quiser - não importa a plataforma ou compilação! Poucos idiomas oferecem essa conveniência. Menciono isso porque estou lutando contra um perdendo muito a batalha com o Java por causa desse problema enlouquecedor ultimamente.)

O problema é que não sei como acessar um módulo Perl do Python - exceto, isto é, usando um callout shell ou um pipe de dois lados. Para esse fim, forneci a você um script de trabalho completo chamado ucsort, que você pode chamar para fazer exatamente o que pediu com perfeita facilidade.

Este script é 100% compatível com o Unicode Collation Algorithm completo , com todas as opções de personalização suportadas !! E se você tiver um módulo opcional instalado ou executar Perl 5.13 ou superior, terá acesso total aos locais CLDR fáceis de usar. Ver abaixo.

Demonstração

Imagine um conjunto de entrada ordenado desta forma:

b o i j n l m å y e v s k h d f g t ö r x p z a ä c u q

Uma classificação padrão por ponto de código produz:

a b c d e f g h i j k l m n o p q r s t u v x y z ä å ö

o que está incorreto no livro de todos. Usando meu script, que usa o algoritmo de agrupamento Unicode, você obtém esta ordem:

% perl ucsort /tmp/swedish_alphabet | fmt
a å ä b c d e f g h i j k l m n o ö p q r s t u v x y z

Essa é a classificação UCA padrão. Para obter a localidade sueca, chame ucsort desta forma:

% perl ucsort --locale=sv /tmp/swedish_alphabet | fmt
a b c d e f g h i j k l m n o p q r s t u v x y z å ä ö

Aqui está uma demonstração de entrada melhor. Primeiro, o conjunto de entrada:

% fmt /tmp/swedish_set
cTD cDD Cöd Cbd cAD cCD cYD Cud cZD Cod cBD Cnd cQD cFD Ced Cfd cOD
cLD cXD Cid Cpd cID Cgd cVD cMD cÅD cGD Cqd Cäd cJD Cdd Ckd cÖD cÄD
Ctd Czd Cxd cHD cND cKD Cvd Chd Cyd cUD Cld Cmd cED Crd Cad Cåd Ccd
cRD cSD Csd Cjd cPD

Por ponto de código, isso classifica desta forma:

Cad Cbd Ccd Cdd Ced Cfd Cgd Chd Cid Cjd Ckd Cld Cmd Cnd Cod Cpd Cqd
Crd Csd Ctd Cud Cvd Cxd Cyd Czd Cäd Cåd Cöd cAD cBD cCD cDD cED cFD
cGD cHD cID cJD cKD cLD cMD cND cOD cPD cQD cRD cSD cTD cUD cVD cXD
cYD cZD cÄD cÅD cÖD

Mas usar o UCA padrão faz com que seja classificado da seguinte maneira:

% ucsort /tmp/swedish_set | fmt
cAD Cad cÅD Cåd cÄD Cäd cBD Cbd cCD Ccd cDD Cdd cED Ced cFD Cfd cGD
Cgd cHD Chd cID Cid cJD Cjd cKD Ckd cLD Cld cMD Cmd cND Cnd cOD Cod
cÖD Cöd cPD Cpd cQD Cqd cRD Crd cSD Csd cTD Ctd cUD Cud cVD Cvd cXD
Cxd cYD Cyd cZD Czd

Mas na localidade sueca, desta forma:

% ucsort --locale=sv /tmp/swedish_set | fmt
cAD Cad cBD Cbd cCD Ccd cDD Cdd cED Ced cFD Cfd cGD Cgd cHD Chd cID
Cid cJD Cjd cKD Ckd cLD Cld cMD Cmd cND Cnd cOD Cod cPD Cpd cQD Cqd
cRD Crd cSD Csd cTD Ctd cUD Cud cVD Cvd cXD Cxd cYD Cyd cZD Czd cÅD
Cåd cÄD Cäd cÖD Cöd

Se você preferir letras maiúsculas antes de letras minúsculas, faça o seguinte:

% ucsort --upper-before-lower --locale=sv /tmp/swedish_set | fmt
Cad cAD Cbd cBD Ccd cCD Cdd cDD Ced cED Cfd cFD Cgd cGD Chd cHD Cid
cID Cjd cJD Ckd cKD Cld cLD Cmd cMD Cnd cND Cod cOD Cpd cPD Cqd cQD
Crd cRD Csd cSD Ctd cTD Cud cUD Cvd cVD Cxd cXD Cyd cYD Czd cZD Cåd
cÅD Cäd cÄD Cöd cÖD

Ordenações Personalizadas

Você pode fazer muitas outras coisas com o ucsort . Por exemplo, aqui está como classificar títulos em inglês:

% ucsort --preprocess='s/^(an?|the)\s+//i' /tmp/titles
Anathem
The Book of Skulls
A Civil Campaign
The Claw of the Conciliator
The Demolished Man
Dune
An Early Dawn
The Faded Sun: Kesrith
The Fall of Hyperion
A Feast for Crows
Flowers for Algernon
The Forbidden Tower
Foundation and Empire
Foundations Edge
The Goblin Reservation
The High Crusade
Jack of Shadows
The Man in the High Castle
The Ringworld Engineers
The Robots of Dawn
A Storm of Swords
Stranger in a Strange Land
There Will Be Time
The White Dragon

Você precisará do Perl 5.10.1 ou superior para executar o script em geral. Para suporte local, você deve instalar o módulo CPAN opcional Unicode::Collate::Locale. Como alternativa, você pode instalar uma versão de desenvolvimento do Perl, 5.13+, que inclui esse módulo padrão.

Convenções de chamada

Este é um protótipo rápido, então ucsort está quase totalmente sub (der) documentado. Mas este é o seu SINOPSE de quais interruptores / opções ele aceita na linha de comando:

    # standard options
    --help|?
    --man|m
    --debug|d

    # collator constructor options
    --backwards-levels=i
    --collation-level|level|l=i
    --katakana-before-hiragana
    --normalization|n=s
    --override-CJK=s
    --override-Hangul=s
    --preprocess|P=s
    --upper-before-lower|u
    --variable=s

    # program specific options
    --case-insensitive|insensitive|i
    --input-encoding|e=s
    --locale|L=s
    --paragraph|p
    --reverse-fields|last
    --reverse-output|r
    --right-to-left|reverse-input

Sim, ok: essa é realmente a lista de argumentos que uso para a chamada para Getopt::Long, mas essa é a ideia. :)

Se você puder descobrir como chamar os módulos da biblioteca Perl diretamente do Python sem chamar um script Perl, faça-o com certeza. Eu só não sei como. Adoraria aprender como.

Nesse ínterim, acredito que este script fará o que você precisa em todas as suas particularidades - e muito mais! Agora eu uso isso para toda a classificação de texto. Ele finalmente faz o que eu precisava de um longo, longo tempo.

A única desvantagem é que o --localeargumento faz com que o desempenho diminua, embora seja rápido o suficiente para classificação normal, não local, mas ainda 100% compatível com UCA . Uma vez que carrega tudo na memória, você provavelmente não deseja usar isso em documentos gigabyte. Eu o uso várias vezes ao dia, e com certeza é ótimo ter finalmente uma classificação sensata de texto.

tchrist
fonte
2
Por que diabos você chamaria um script Perl para fazer algo para o qual existem bibliotecas Python?
Lennart Regebro
2
Porque eu não sabia que havia uma biblioteca Python, é por isso!
tchrist
@Lennart: Eu realmente prefiro bibliotecas nativas, ou no máximo aquelas vinculadas a uma API C e carregadas dinamicamente (que você às vezes precisa). Não achei as várias soluções PyPerl e Inline :: Perl muito convincentes, ou robustas, ou flexíveis. Ou alguma coisa. Eles simplesmente não parecem certos por alguns motivos. Eu tentei isso pela última vez quando precisei de uma boa detecção de conjuntos de caracteres (que nunca consegui, infelizmente).
tchrist
4
Usar Perl dentro do Python é apenas um vício.
Utku Zihnioglu
1
Uau. Sim - parece Perl para mim, na verdade, vemos que agora há mais de duas maneiras de fazer as coisas :) Mas chamar C de Python geralmente não implica os tipos de dependências adicionadas e problemas de suporte prático que chamar de Perl implicaria, então é terrivelmente difícil ver muita necessidade de fazer dessa maneira.
nealmcb
0

Ele está longe de ser uma solução completa para seu caso de uso, mas você pode dar uma olhada no script unaccent.py de effbot.org. O que basicamente faz é remover todos os acentos de um texto. Você pode usar esse texto 'higienizado' para classificar em ordem alfabética. (Para uma descrição melhor, consulte esta página.)

Mark van Lent
fonte