Come faccio a ordinare alfabeticamente le stringhe Unicode in Python?


97

Python ordina per valore di byte per impostazione predefinita, il che significa che é viene dopo z e altre cose altrettanto divertenti. Qual è il modo migliore per ordinare alfabeticamente in Python?

C'è una libreria per questo? Non ho trovato niente. Preferibilmente, l'ordinamento dovrebbe avere il supporto della lingua in modo che capisca che åäö dovrebbe essere ordinato dopo z in svedese, ma che ü dovrebbe essere ordinato per u, ecc. Il supporto Unicode è quindi praticamente un requisito.

Se non è disponibile una libreria, qual è il modo migliore per farlo? Basta fare una mappatura dalla lettera a un valore intero e mappare la stringa a un elenco di interi con quello?


11
Nota che questo dipende ancora di più dalle impostazioni locali: in svedese (come dici tu) "Ä" viene dopo "Z", ma in tedesco "Ä" è solitamente ordinato come "AE".
balpha

@ Georg: C'era una ragione per cui hai aperto una taglia su questo? La locale.strcollrisposta è corretta quando è necessario l'ordinamento Unicode utilizzando le impostazioni internazionali dell'utente e l'ICU risponde a ciò che si desidera quando è necessario più di questo (regole di confronto che utilizzano più di una locale). La maggior parte delle volte, vuoi locale.strcoll.
Glenn Maynard

@ Glenn: volevo sapere come locale.strcollfunziona e soprattutto cosa fa meglio ICU rispetto alla funzione Python. Fondamentalmente un po 'più di attenzione per la domanda.
Georg Schölly

1
@ Georg: Ultimamente ho giocato molto con l'algoritmo di collazione Unicode, come puoi vedere dalla mia risposta. È davvero eccellente poter, ad esempio, ordinare --locale=de__phonebookquando ne hai bisogno. Il modulo Perl supera la suite di test UCA e lo script che ho fornito rende molto più facile giocare con l'intero UCA più tutte le sue opzioni, comprese le impostazioni locali, solo dalla riga di comando. Potrebbe non rispondere alla domanda, ma dovrebbe comunque essere molto interessante. Se sei in Svizzera, sono sicuro che potresti usare la flessibilità. :)
tchrist

Risposte:


75

La libreria ICU di IBM lo fa (e molto altro). Ha collegamenti Python: PyICU .

Aggiornamento : la differenza principale nell'ordinamento tra ICU ed locale.strcollè che ICU utilizza l'intero algoritmo di confronto Unicode mentre strcollutilizza ISO 14651 .

Le differenze tra questi due algoritmi sono riassunte brevemente qui: http://unicode.org/faq/collation.html#13 . Questi sono casi speciali piuttosto esotici, che raramente dovrebbero avere importanza nella pratica.

>>> 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']

Funziona allo stesso modo per Python 2 e Python 3? Ho usato locale.strxfrmdalla risposta di u0b34a0f6ae e sembra funzionare ed è molto più elegante e non richiede alcun software aggiuntivo.
sup

Non funziona con Python3 per me, sudo pip3 install PyICUnon si installa e così fa per Python2.
imrek

Ho dovuto installare libicu-devel.x86_64 per compilare e installare pyICU da Pip. Funziona, sebbene l'output dell'ultimo comando 'ordinato' sia: ['a', '\ xc3 \ xa4', 'b', 'c']
Mike Stoddart

53

Non lo vedo nelle risposte. La mia applicazione ordina in base alle impostazioni locali utilizzando la libreria standard di python. È abbastanza facile.

# 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)

Domanda a Lennart e ad altri rispondenti: nessuno conosce il "locale" o non è all'altezza di questo compito?


A proposito 1) Non credo che locale.strxfrm sia guasto per `str 'codificato UTF-8; Ho eseguito un benchmark per applicazione e ho concluso che l'uso di cmp = strcoll su oggetti Unicode è più economico che decodificare tutto in UTF-8 e usare key = strxfrm
u0b34a0f6ae

6
A proposito 2) Il modulo locale funzionerà solo con le impostazioni locali generate (per una macchina Linux), non con qualsiasi locale arbitraria. "locale -a" ti dirà quale
u0b34a0f6ae

6
@ Georg: credo che la localizzazione supporti solo una semplice mappatura sottostringa-> collating_element. Non gestisce cose come espansioni (æ ordinate come "ae"), ordinamento accento francese (lettere ordinate da sinistra a destra, ma accenti da destra a sinistra), riorganizzazione e probabilmente qualcun altro. Dettagli qui (set completo di funzionalità UCA): unicode.org/reports/tr10 e qui (confronto locale): chm.tu-dresden.de/edv/manuals/aix/files/aixfiles/LC_COLLATE.htm
Rafał Dowgird

2
Per rispondere chiaramente alla domanda: Sì, dipende dal compito. Apparentemente ci sono alcuni casi speciali che l'algoritmo di confronto Unicode completo gestisce meglio, ma a meno che tu non sappia già che è probabile che non te ne accorga.
Lennart Regebro

1
Il problema più grande qui è: devi impostare la locale globalmente per l'intera applicazione. - Non puoi averlo solo per il confronto a portata di mano.
Robert Siemer

9

Prova l' algoritmo di confronto Python Unicode di James Tauber . Potrebbe non funzionare esattamente come desideri, ma vale la pena dare un'occhiata. Per ulteriori informazioni sui problemi, vedere questo post di Christopher Lenz.


Almeno questo risolve il problema generico. Immagino che potrebbero essere create anche versioni sensibili alla lingua dell'elenco di confronto.
Lennart Regebro

Questo non ti consente di specificare la locale e il file di configurazione di riferimento causa un'eccezione ValueError.
thebjorn

8

Potresti anche essere interessato a pyuca :

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

Sebbene non sia certamente il modo più esatto, è un modo molto semplice per farlo almeno un po 'bene. Inoltre, batte le impostazioni locali in una webapp poiché le impostazioni locali non sono thread-safe e definiscono le impostazioni della lingua a livello di processo. È anche più facile da configurare rispetto a PyICU che si basa su una libreria C esterna.

Ho caricato lo script su GitHub poiché l'originale era inattivo al momento della stesura di questo articolo e ho dovuto ricorrere a cache Web per ottenerlo:

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

Ho usato con successo questo script per ordinare in modo corretto il testo tedesco / francese / italiano in un modulo plone.


+1 per pyuca. È abbastanza veloce (3 secondi per ordinare 28000 parole), è puro Python e non richiede dipendenze.
michaelmeyer

7

Una risposta sommaria e estesa:

locale.strcollsotto Python 2, e locale.strxfrmrisolverà di fatto il problema e fa un buon lavoro, assumendo che tu abbia installato il locale in questione. L'ho testato anche su Windows, dove i nomi delle impostazioni locali sono diversi, ma d'altra parte sembra che tutti i locali supportati siano installati di default.

ICUnon necessariamente lo fa meglio nella pratica, tuttavia fa molto di più . In particolare, supporta gli splitter che possono dividere in parole testi in lingue diverse. Questo è molto utile per le lingue che non hanno separatori di parole. Avrai bisogno di un corpus di parole da usare come base per la divisione, perché non è incluso, però.

Ha anche nomi lunghi per le impostazioni locali in modo da poter ottenere bei nomi visualizzati per le impostazioni locali, supporto per altri calendari oltre a Gregorian (anche se non sono sicuro che l'interfaccia Python lo supporti) e tonnellate e tonnellate di altri supporti locali più o meno oscuri .

Quindi, tutto sommato: se si desidera ordinare alfabeticamente e in base alla località, è possibile utilizzare il localemodulo, a meno che non si abbiano requisiti speciali o si necessiti anche di funzionalità più dipendenti dalla località, come il separatore di parole.


6

Vedo che le risposte hanno già svolto un ottimo lavoro, volevo solo sottolineare un'inefficienza di codifica in Human Sort . Per applicare una traduzione selettiva carattere per carattere a una stringa Unicode s, utilizza il codice:

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

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

Python ha un modo molto migliore, più veloce e più conciso per eseguire questo compito ausiliario (su stringhe Unicode - il metodo analogo per le stringhe di byte ha una specifica diversa e un po 'meno utile! -):

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

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

Il dict che passi al translatemetodo ha ordinali Unicode (non stringhe) come chiavi, motivo per cui abbiamo bisogno di quel passaggio di ricostruzione dal carattere originale al carattere spec_dict. (I valori nel dict che passi per tradurre [al contrario delle chiavi, che devono essere ordinali] possono essere ordinali Unicode, stringhe Unicode arbitrarie o Nessuno per rimuovere il carattere corrispondente come parte della traduzione, quindi è facile specificare "ignora un un certo carattere per scopi di ordinamento "," mappare ä a ae per scopi di ordinamento "e simili).

In Python 3, puoi ottenere il passaggio di "ricostruzione" più semplicemente, ad esempio:

spec_dict = ''.maketrans(spec_dict)

Consulta la documentazione per altri modi in cui puoi utilizzare questo maketransmetodo statico in Python 3.


Questo metodo è carino ma non ti consente di posizionare á tra az eb
Barney


1

Ultimamente ho utilizzato zope.ucol ( https://pypi.python.org/pypi/zope.ucol ) per questo compito. Ad esempio, ordinando il ß tedesco:

>>> 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 avvolge anche l'ICU, quindi sarebbe un'alternativa a PyICU.


1

Una soluzione UCA completa

Il modo più semplice, facile e diretto per farlo è fare un richiamo al modulo della libreria Perl, Unicode :: Collate :: Locale , che è una sottoclasse del modulo Unicode :: Collate standard. Tutto quello che devi fare è passare al costruttore un valore locale di "xv"per la Svezia.

(Potresti non apprezzarlo necessariamente per il testo svedese, ma poiché Perl usa caratteri astratti, puoi usare qualsiasi punto di codice Unicode che desideri, indipendentemente dalla piattaforma o dalla build! Poche lingue offrono tale comodità. Lo menziono perché ho combattuto un ultimamente ha perso molte battaglie con Java per questo problema esasperante.)

Il problema è che non so come accedere a un modulo Perl da Python, a parte, cioè, dall'utilizzo di un callout della shell o di una pipe a due lati. A tal fine, ti ho quindi fornito uno script funzionante completo chiamato ucsort che puoi chiamare per fare esattamente ciò che hai richiesto con perfetta facilità.

Questo script è compatibile al 100% con l' algoritmo di confronto Unicode completo , con tutte le opzioni di personalizzazione supportate !! E se si dispone di un modulo opzionale installato o si esegue Perl 5.13 o superiore, si ha pieno accesso alle versioni locali CLDR di facile utilizzo. Vedi sotto.

Dimostrazione

Immagina un set di input ordinato in questo modo:

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

Un ordinamento predefinito per punto di codice restituisce:

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 ä å ö

che non è corretto dal libro di tutti. Usando il mio script, che utilizza l'algoritmo di confronto Unicode, ottieni questo ordine:

% 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

Questo è l'ordinamento UCA predefinito. Per ottenere la lingua svedese, chiama ucsort in questo modo:

% 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 å ä ö

Ecco una demo di input migliore. Innanzitutto, il set di input:

% 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

Per punto di codice, ordina in questo modo:

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

Ma l'utilizzo dell'UCA predefinito consente di ordinare in questo modo:

% 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

Ma nella lingua svedese, in questo modo:

% 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 preferisci ordinare le maiuscole prima delle minuscole, fai questo:

% 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

Ordinamenti personalizzati

Puoi fare molte altre cose con ucsort . Ad esempio, ecco come ordinare i titoli in inglese:

% 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

Avrai bisogno di Perl 5.10.1 o superiore per eseguire lo script in generale. Per il supporto locale, è necessario installare il modulo CPAN opzionale Unicode::Collate::Locale. In alternativa, puoi installare una versione di sviluppo di Perl, 5.13+, che include quel modulo in modo standard.

Convenzioni di chiamata

Questo è un prototipo rapido, quindi ucsort è per lo più sotto (der) documentato. Ma questa è la sua SINOSSI di quali opzioni / opzioni accetta sulla riga di 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

Sì, ok: questo è davvero l'elenco degli argomenti che uso per la chiamata a Getopt::Long, ma hai capito. :)

Se riesci a capire come chiamare i moduli della libreria Perl da Python direttamente senza chiamare uno script Perl, fallo in ogni caso. Non so proprio come fare. Mi piacerebbe imparare come.

Nel frattempo, credo che questo script farà ciò di cui hai bisogno in tutti i suoi particolari e anche di più! Ora lo uso per tutto l'ordinamento del testo. E , infine, fa quello che ho bisogno per un lungo, lungo tempo.

L'unico svantaggio è che l' --localeargomento fa sì che le prestazioni vadano giù per i tubi, sebbene sia abbastanza veloce per l' ordinamento regolare, non locale ma ancora conforme al 100% UCA . Dato che carica tutto in memoria, probabilmente non vorrai usarlo su documenti gigabyte. Lo uso molte volte al giorno, e finalmente è fantastico avere un ordinamento di testo sano.


2
Perché mai chiameresti uno script Perl per fare qualcosa per cui esistono le librerie Python?
Lennart Regebro

2
Perché non sapevo ci fosse una libreria Python, ecco perché!
tchrist

@ Lennart: preferisco davvero le librerie native, o al massimo quelle collegate a un'API C e caricate dinamicamente (che a volte ti servono). Non ho trovato le varie soluzioni PyPerl e Inline :: Perl molto convincenti, robuste o flessibili. O qualcosa. Semplicemente non si sentono a posto per alcuni motivi. L'ho provato l'ultima volta quando avevo bisogno di un buon rilevamento del set di caratteri (che non ho mai avuto, ahimè).
Cristo

4
Usare Perl in Python è solo dipendenza.
Utku Zihnioglu

1
Wow. Sì, a me sembra Perl, infatti vediamo che ora ci sono più di due modi per fare le cose :) Ma chiamare C da Python in genere non implica il tipo di dipendenze aggiuntive e problemi di supporto pratico che la chiamata Perl comporterebbe, quindi è terribilmente difficile vedere molte richieste per farlo in questo modo.
nealmcb

0

Non è affatto una soluzione completa per il vostro caso d'uso, ma si potrebbe dare un'occhiata al unaccent.py script da effbot.org. Quello che fondamentalmente fa è rimuovere tutti gli accenti da un testo. Puoi usare quel testo "disinfettato" per ordinare alfabeticamente. (Per una descrizione migliore vedere questa pagina.)


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.