Bagaimana cara mengurutkan string unicode menurut abjad dengan Python?

97

Python mengurutkan berdasarkan nilai byte secara default, yang berarti é muncul setelah z dan hal-hal lucu lainnya. Apa cara terbaik untuk mengurutkan menurut abjad dengan Python?

Apakah ada perpustakaan untuk ini? Saya tidak dapat menemukan apa pun. Pengurutan yang disukai harus memiliki dukungan bahasa sehingga memahami bahwa åäö harus diurutkan setelah z dalam bahasa Swedia, tetapi ü harus diurutkan berdasarkan u, dll. Dukungan Unicode dengan demikian cukup banyak merupakan persyaratan.

Jika tidak ada perpustakaan untuk itu, apa cara terbaik untuk melakukannya? Buat saja pemetaan dari huruf ke nilai integer dan petakan string ke daftar integer dengan itu?

Lennart Regebro
sumber
11
Perhatikan bahwa ini bahkan lebih bergantung pada lokal: Dalam bahasa Swedia (seperti yang Anda sebutkan) "Ä" muncul setelah "Z", tetapi dalam bahasa Jerman, "Ä" biasanya diurutkan sebagai "AE".
balpha
@Georg: Apakah ada alasan Anda membuka bounty untuk ini? The locale.strcolljawabannya benar ketika Anda membutuhkan Unicode menyortir menggunakan lokal pengguna, dan jawaban ICU apa yang Anda inginkan ketika Anda membutuhkan lebih dari itu (pemeriksaan menggunakan lebih dari satu lokal). Sering kali, Anda menginginkannya locale.strcoll.
Glenn Maynard
@ Glenn: Saya ingin tahu seberapa baik locale.strcollbekerja dan terutama apa yang ICU lakukan lebih baik daripada fungsi Python. Pada dasarnya lebih banyak perhatian untuk pertanyaan itu.
Georg Schölly
1
@Georg: Saya telah bermain-main dengan Unicode Collation Algorithm akhir-akhir ini, seperti yang Anda lihat dari jawaban saya. Sungguh luar biasa untuk dapat, misalnya, memilah-milah --locale=de__phonebooksaat Anda membutuhkannya. Modul Perl melewati rangkaian pengujian UCA, dan skrip yang saya sediakan membuatnya lebih mudah untuk dimainkan dengan seluruh UCA ditambah semua opsinya termasuk lokal, hanya dari baris perintah. Mungkin tidak menjawab dengan pertanyaan, tetapi masih harus sangat menarik. Jika Anda berada di Swiss, saya yakin Anda dapat menggunakan fleksibilitas. :)
tchrist

Jawaban:

75

Perpustakaan ICU IBM melakukan itu (dan banyak lagi). Ini memiliki binding Python: PyICU .

Pembaruan : Perbedaan inti dalam pengurutan antara ICU dan locale.strcollICU menggunakan Algoritma Penyusunan Unicode penuh saat strcollmenggunakan ISO 14651 .

Perbedaan antara kedua algoritme tersebut dirangkum secara singkat di sini: http://unicode.org/faq/collation.html#13 . Ini adalah kasus khusus yang agak eksotis, yang dalam praktiknya jarang menjadi masalah.

>>> 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
sumber
Apakah ini bekerja sama untuk Python 2 dan Python 3? Saya menggunakan locale.strxfrmjawaban dari u0b34a0f6ae dan tampaknya berfungsi serta jauh lebih elegan dan tidak memerlukan perangkat lunak tambahan.
sup
Tidak berfungsi dengan Python3 untuk saya, sudo pip3 install PyICUgagal dipasang dan begitu juga untuk Python2.
imrek
Saya harus menginstal libicu-devel.x86_64 untuk pyICU untuk mengkompilasi dan menginstal dari Pip. Ini berfungsi, meskipun output dari perintah 'diurutkan' terakhir adalah: ['a', '\ xc3 \ xa4', 'b', 'c']
Mike Stoddart
53

Saya tidak melihat ini dalam jawaban. Aplikasi saya menyortir menurut lokal menggunakan pustaka standar python. Sangat mudah.

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

Pertanyaan untuk Lennart dan penjawab lainnya: Apakah tidak ada yang tahu 'lokal' atau tidak untuk tugas ini?

u0b34a0f6ae
sumber
By the way 1) Saya tidak berpikir locale.strxfrm rusak untuk UTF-8 encoded `str '; Saya membandingkan dengan aplikasi dan menyimpulkan bahwa menggunakan cmp = strcoll pada objek unicode lebih murah daripada mendekode semua ke UTF-8 dan menggunakan key = strxfrm
u0b34a0f6ae
6
By the way 2) Modul lokal hanya akan bekerja dengan lokal yang Anda buat (untuk komputer Linux), bukan sembarang lokal. "locale -a" akan memberi tahu Anda yang
u0b34a0f6ae
6
@Georg: Saya percaya bahwa lokal hanya mendukung substring sederhana-> pemetaan collating_element. Itu tidak menangani hal-hal seperti perluasan (æ diurutkan sebagai "ae"), pengurutan aksen Prancis (huruf diurutkan dari kiri ke kanan, tetapi aksen kanan-ke-kiri), penataan ulang dan mungkin beberapa lagi. Detail di sini (rangkaian fitur UCA lengkap): unicode.org/reports/tr10 dan di sini (pemeriksaan lokal): chm.tu-dresden.de/edv/manuals/aix/files/aixfiles/LC_COLLATE.htm
Rafał Dowgird
2
Untuk jelas menjawab pertanyaan: Ya itu adalah untuk tugas itu. Rupanya ada beberapa kasus khusus yang ditangani lebih baik oleh Algoritma Penyatuan Unicode lengkap, tetapi kecuali Anda sudah tahu bahwa kemungkinan besar Anda tidak akan menyadarinya.
Lennart Regebro
1
Masalah terbesar di sini adalah: Anda harus menyetel lokal secara global untuk seluruh aplikasi. - Anda tidak bisa hanya memilikinya untuk perbandingan.
Robert Siemer
9

Coba Algoritma Penyusunan Python Unicode James Tauber . Ini mungkin tidak persis seperti yang Anda inginkan, tetapi tampaknya menarik untuk dilihat. Untuk sedikit lebih banyak informasi tentang masalah ini, lihat posting ini oleh Christopher Lenz.

Vinay Sajip
sumber
Itu setidaknya memperbaiki masalah umum. Saya kira versi sensitif bahasa dari daftar pemeriksaan juga dapat dibuat.
Lennart Regebro
Ini tidak memungkinkan Anda menentukan lokal, dan file konfigurasi referensi menyebabkan ValueError.
thebjorn
8

Anda mungkin juga tertarik dengan pyuca :

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

Meskipun ini tentunya bukan cara yang paling tepat, ini adalah cara yang sangat sederhana untuk setidaknya membuatnya agak benar. Ini juga mengalahkan lokal di aplikasi web karena lokal bukanlah threadsafe dan menyetel pengaturan bahasa di seluruh proses. Ini juga lebih mudah untuk mengatur daripada PyICU yang mengandalkan perpustakaan C eksternal.

Saya mengunggah skrip ke github karena aslinya sedang tidak aktif pada saat penulisan ini dan saya harus menggunakan cache web untuk mendapatkannya:

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

Saya berhasil menggunakan skrip ini untuk mengurutkan teks Jerman / Prancis / Italia dengan baik dalam modul plone.

href_
sumber
1 untuk pyuca. Ini cukup cepat (3 detik untuk mengurutkan 28000 kata), adalah python murni, dan tidak memerlukan ketergantungan.
michaelmeyer
7

Ringkasan dan jawaban tambahan:

locale.strcolldi bawah Python 2, dan locale.strxfrmsebenarnya akan menyelesaikan masalah, dan melakukan pekerjaan dengan baik, dengan asumsi Anda telah menginstal lokal yang dimaksud. Saya mengujinya di bawah Windows juga, di mana nama lokal yang membingungkan berbeda, tetapi di sisi lain tampaknya memiliki semua lokal yang didukung diinstal secara default.

ICUtidak selalu melakukan ini dengan lebih baik dalam praktiknya, namun lebih dari itu . Terutama memiliki dukungan untuk pemisah yang dapat membagi teks dalam berbagai bahasa menjadi kata-kata. Ini sangat berguna untuk bahasa yang tidak memiliki pemisah kata. Anda harus memiliki kumpulan kata untuk digunakan sebagai dasar pemisahan, karena itu tidak termasuk.

Ini juga memiliki nama panjang untuk lokal sehingga Anda bisa mendapatkan nama tampilan yang cantik untuk lokal, dukungan untuk kalender lain selain Gregorian (meskipun saya tidak yakin antarmuka Python mendukung itu) dan ton dan ton dukungan lokal lainnya yang kurang lebih tidak jelas .

Jadi semuanya: Jika Anda ingin mengurutkan menurut abjad dan bergantung pada lokal, Anda dapat menggunakan localemodul, kecuali Anda memiliki persyaratan khusus, atau juga memerlukan fungsionalitas yang lebih bergantung pada lokal, seperti kata-kata splitter.

Lennart Regebro
sumber
6

Saya melihat jawabannya telah melakukan pekerjaan yang sangat baik, hanya ingin menunjukkan satu ketidakefisienan pengkodean di Human Sort . Untuk menerapkan terjemahan char-by-char selektif ke string unicode s, itu menggunakan kode:

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

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

Python memiliki cara yang jauh lebih baik, lebih cepat dan lebih ringkas untuk melakukan tugas tambahan ini (pada string Unicode - metode analog untuk string byte memiliki spesifikasi yang berbeda dan agak kurang membantu! -):

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

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

Dikt yang Anda berikan ke translate metode memiliki ordinal Unicode (bukan string) sebagai kunci, itulah sebabnya kita membutuhkan langkah membangun kembali dari char-to-char asli spec_dict. (Nilai dalam dict yang Anda berikan untuk diterjemahkan [sebagai lawan kunci, yang harus berupa ordinal] bisa berupa ordinal Unicode, string Unicode arbitrer, atau None untuk menghapus karakter terkait sebagai bagian dari terjemahan, sehingga mudah untuk menentukan "abaikan a karakter tertentu untuk keperluan penyortiran "," petakan ä ke ae untuk tujuan penyortiran ", dan sejenisnya).

Di Python 3, Anda bisa mendapatkan langkah "membangun kembali" dengan lebih sederhana, misalnya:

spec_dict = ''.maketrans(spec_dict)

Lihat dokumen untuk mengetahui cara lain menggunakan maketransmetode statis ini di Python 3.

Alex Martelli
sumber
Metode ini bagus tetapi tidak memungkinkan Anda menempatkan á antara az dan b
Barney
1

Akhir-akhir ini saya menggunakan zope.ucol ( https://pypi.python.org/pypi/zope.ucol ) untuk tugas ini. Misalnya, mengurutkan bahasa jerman ß:

>>> 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 juga membungkus ICU, jadi akan menjadi alternatif untuk PyICU.

Brian Sutherland
sumber
1

Solusi UCA Lengkap

Cara termudah, termudah, dan paling mudah untuk melakukannya adalah dengan membuat panggilan ke modul perpustakaan Perl, Unicode :: Collate :: Locale , yang merupakan subkelas dari modul Unicode :: Collate standar . Yang perlu Anda lakukan adalah meneruskan konstruktor dengan nilai lokal "xv"untuk Swedia.

(Anda mungkin tidak perlu menghargai ini untuk teks bahasa Swedia, tetapi karena Perl menggunakan karakter abstrak, Anda dapat menggunakan titik kode Unicode apa pun sesuka Anda - tidak peduli platform atau build! Beberapa bahasa menawarkan kemudahan seperti itu. Saya menyebutkannya karena saya telah melawan kalah dalam pertempuran dengan Java karena masalah yang menjengkelkan akhir-akhir ini.)

Masalahnya adalah saya tidak tahu bagaimana mengakses modul Perl dari Python - selain itu, dari menggunakan shell callout atau pipa dua sisi. Untuk itu, oleh karena itu saya telah memberi Anda skrip kerja lengkap yang disebut ucsort yang dapat Anda panggil untuk melakukan apa yang Anda minta dengan mudah.

Skrip ini 100% sesuai dengan Algoritma Penyatuan Unicode lengkap , dengan semua opsi penyesuaian yang didukung !! Dan jika Anda memiliki modul opsional yang diinstal atau menjalankan Perl 5.13 atau yang lebih baik, Anda memiliki akses penuh ke lokal CLDR yang mudah digunakan. Lihat di bawah.

Demonstrasi

Bayangkan satu set input yang diurutkan dengan cara ini:

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

Urutan default berdasarkan hasil poin kode:

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

yang tidak benar oleh buku semua orang. Menggunakan skrip saya, yang menggunakan Unicode Collation Algorithm, Anda mendapatkan urutan ini:

% 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

Itu adalah jenis UCA default. Untuk mendapatkan lokal Swedia, panggil ucsort dengan cara ini:

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

Ini adalah demo masukan yang lebih baik. Pertama, set 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

Menurut kode poin, seperti ini:

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

Tetapi menggunakan UCA default membuatnya seperti ini:

% 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

Namun dalam bahasa Swedia, begini:

% 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

Jika Anda lebih suka mengurutkan huruf besar sebelum huruf kecil, lakukan ini:

% 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

Macam Disesuaikan

Anda dapat melakukan banyak hal lain dengan ucsort . Misalnya, berikut cara mengurutkan judul dalam bahasa Inggris:

% 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

Anda membutuhkan Perl 5.10.1 atau yang lebih baik untuk menjalankan skrip secara umum. Untuk dukungan lokal, Anda harus memasang modul CPAN opsional Unicode::Collate::Locale. Sebagai alternatif, Anda dapat menginstal versi pengembangan Perl, 5.13+, yang menyertakan modul itu secara standar.

Konvensi Panggilan

Ini adalah prototipe cepat, jadi ucsort sebagian besar tidak didokumentasikan. Tetapi ini adalah SINOPSIS dari switch / opsi apa yang diterimanya pada baris perintah:

    # 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

Ya, ok: itu benar-benar daftar argumen yang saya gunakan untuk panggilan Getopt::Long, tetapi Anda mengerti. :)

Jika Anda dapat mengetahui cara memanggil modul pustaka Perl dari Python secara langsung tanpa memanggil skrip Perl, lakukanlah. Saya hanya tidak tahu bagaimana diri saya sendiri. Saya ingin mempelajari caranya.

Sementara itu, saya yakin skrip ini akan melakukan apa yang perlu Anda lakukan secara khusus - dan banyak lagi! Saya sekarang menggunakan ini untuk semua penyortiran teks. Itu akhirnya melakukan apa yang saya butuhkan untuk waktu yang sangat, sangat lama.

Satu-satunya downside adalah bahwa --localeargumen menyebabkan kinerja turun tabung, meskipun cukup cepat untuk penyortiran reguler, non-lokal tetapi masih 100% sesuai UCA . Karena memuat semua yang ada di memori, Anda mungkin tidak ingin menggunakan ini pada dokumen gigabyte. Saya menggunakannya berkali-kali sehari, dan itu pasti bagus karena akhirnya teks yang masuk akal.

tchrist
sumber
2
Mengapa Anda memanggil skrip Perl untuk melakukan sesuatu yang ada pustaka Python?
Lennart Regebro
2
Karena saya tidak tahu ada adalah sebuah perpustakaan Python, itu sebabnya!
tchrist
@ Lennart: Saya lebih suka perpustakaan asli, atau paling banyak yang ditautkan ke C API dan dimuat secara dinamis (yang terkadang Anda perlukan). Saya belum menemukan berbagai solusi PyPerl dan Inline :: Perl sangat meyakinkan, atau kuat, atau fleksibel. Atau sesuatu. Mereka hanya merasa tidak enak karena beberapa alasan. Saya terakhir mencoba ini ketika saya membutuhkan deteksi charset yang baik (yang tidak pernah saya dapatkan, sayangnya).
tchrist
4
Menggunakan Perl di dalam Python hanyalah kecanduan.
Utku Zihnioglu
1
Wow. Ya - terlihat seperti Perl bagi saya, sebenarnya kami melihat bahwa sekarang ada lebih dari dua cara untuk melakukan sesuatu :) Tetapi memanggil C dari Python tidak secara umum menyiratkan jenis ketergantungan tambahan dan masalah dukungan praktis yang memanggil Perl, jadi ini sangat sulit untuk melihat banyak panggilan untuk melakukannya dengan cara ini.
nealmcb
0

Hal ini jauh dari solusi lengkap untuk kasus penggunaan Anda, tapi Anda bisa melihat di unaccent.py script dari effbot.org. Apa yang pada dasarnya dilakukannya adalah menghilangkan semua aksen dari teks. Anda dapat menggunakan teks 'bersih' itu untuk mengurutkan menurut abjad. (Untuk penjelasan yang lebih baik lihat halaman ini .)

Mark van Lent
sumber