it-swarm-id.com

Abaikan case dalam string Python

Apa cara termudah untuk membandingkan string dengan Python, mengabaikan case?

Tentu saja seseorang dapat melakukannya (str1.lower () <= str2.lower ()), dll., Tetapi ini menciptakan dua string sementara tambahan (dengan overhead alokasi/g-c yang jelas).

Saya kira saya sedang mencari yang setara dengan stricmp ().

[Beberapa konteks lagi diminta, jadi saya akan menunjukkan dengan contoh sepele:]

Misalkan Anda ingin mengurutkan daftar string yang panjang lebar. Anda cukup melakukan list.sort () . Ini adalah perbandingan string O (n * log (n)) dan tidak ada manajemen memori (karena semua String dan elemen daftar adalah semacam smart pointer). Anda senang.

Sekarang, Anda ingin melakukan hal yang sama, tetapi abaikan kasingnya (mari kita sederhanakan dan katakana Semua string adalah ascii, jadi masalah lokal dapat diabaikan) . .lower ()), tetapi kemudian Anda menyebabkan dua alokasi .__ baru per perbandingan, ditambah membebani pengumpul sampah dengan string .__ (yang diturunkan) yang digandakan. Setiap noise manajemen memori seperti itu adalah urutan-of-magnitude lebih lambat dari perbandingan string sederhana.

Sekarang, dengan fungsi seperti di tempat stricmp () -, Anda lakukan: theList.sort (cmp = stricmp). Kamu bahagia lagi.

Masalahnya adalah perbandingan case-insensitive berbasis-Python melibatkan duplikasi string tersirat., Jadi saya mengharapkan untuk menemukan perbandingan berbasis-C (mungkin dalam string modul).

Tidak dapat menemukan hal seperti itu, maka pertanyaannya di sini . (Semoga ini menjelaskan pertanyaan itu).

51
Paul Oyster

Berikut ini adalah tolok ukur yang menunjukkan bahwa menggunakan str.lower lebih cepat dari metode yang diajukan pada jawaban yang diterima (libc.strcasecmp):

#!/usr/bin/env python2.7
import random
import timeit

from ctypes import *
libc = CDLL('libc.dylib') # change to 'libc.so.6' on linux

with open('/usr/share/dict/words', 'r') as wordlist:
    words = wordlist.read().splitlines()
random.shuffle(words)
print '%i words in list' % len(words)

setup = 'from __main__ import words, libc; gc.enable()'
stmts = [
    ('simple sort', 'sorted(words)'),
    ('sort with key=str.lower', 'sorted(words, key=str.lower)'),
    ('sort with cmp=libc.strcasecmp', 'sorted(words, cmp=libc.strcasecmp)'),
]

for (comment, stmt) in stmts:
    t = timeit.Timer(stmt=stmt, setup=setup)
    print '%s: %.2f msec/pass' % (comment, (1000*t.timeit(10)/10))

waktu khas di mesin saya:

235886 words in list
simple sort: 483.59 msec/pass
sort with key=str.lower: 1064.70 msec/pass
sort with cmp=libc.strcasecmp: 5487.86 msec/pass

Jadi, versi dengan str.lower tidak hanya yang tercepat sejauh ini, tetapi juga yang paling portabel dan Pythonic dari semua solusi yang diusulkan di sini . Saya belum membuat profil penggunaan memori, tetapi poster aslinya masih belum memberikan alasan kuat untuk khawatirkan itu. Juga, siapa yang mengatakan bahwa panggilan ke modul libc tidak menduplikasi string?

NB: Metode string lower() juga memiliki keuntungan sebagai dependen-lokal. Sesuatu yang Anda mungkin tidak akan benar ketika menulis solusi "dioptimalkan" Anda sendiri. Meski begitu, karena bug dan fitur yang hilang di Python, perbandingan semacam ini dapat memberi Anda hasil yang salah dalam konteks unicode.

74
user3850

Apakah Anda menggunakan perbandingan ini di jalur yang sangat sering dilakukan dari aplikasi yang sangat sensitif terhadap kinerja? Atau, apakah Anda menjalankan ini pada string yang berukuran megabyte? Jika tidak, maka Anda tidak perlu khawatir tentang kinerja dan cukup gunakan metode .lower ().

Kode berikut menunjukkan bahwa melakukan perbandingan case-insensitive dengan memanggil .lower () pada dua string yang masing-masing berukuran hampir megabyte membutuhkan waktu sekitar 0,009 detik pada komputer desktop 1.8GHz saya:

from timeit import Timer

s1 = "1234567890" * 100000 + "a"
s2 = "1234567890" * 100000 + "B"

code = "s1.lower() < s2.lower()"
time = Timer(code, "from __main__ import s1, s2").timeit(1000)
print time / 1000   # 0.00920499992371 on my machine

Jika memang ini bagian yang sangat penting, kinerja-kritis dari kode, maka saya sarankan menulis fungsi dalam C dan memanggilnya dari kode Python Anda, karena itu akan memungkinkan Anda untuk melakukan pencarian case-sensitive case yang benar-benar efisien. Detail tentang penulisan modul ekstensi C dapat ditemukan di sini: https://docs.python.org/extending/extending.html

7
Eli Courtwright

Pertanyaan Anda menyiratkan bahwa Anda tidak perlu Unicode. Coba cuplikan kode berikut; jika berhasil untuk Anda, Anda sudah selesai:

Python 2.5.2 (r252:60911, Aug 22 2008, 02:34:17)
[GCC 4.3.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import locale
>>> locale.setlocale(locale.LC_COLLATE, "en_US")
'en_US'
>>> sorted("ABCabc", key=locale.strxfrm)
['a', 'A', 'b', 'B', 'c', 'C']
>>> sorted("ABCabc", cmp=locale.strcoll)
['a', 'A', 'b', 'B', 'c', 'C']

Klarifikasi: jika tidak terlihat jelas pada pandangan pertama, locale.strcoll tampaknya merupakan fungsi yang Anda butuhkan, menghindari str.lower atau locale.strxfrm "duplikat" string.

7
tzot

Saya tidak dapat menemukan cara built-in lainnya untuk melakukan perbandingan case-insensitive: Resep python cook-book menggunakan lower ().

Namun Anda harus berhati-hati ketika menggunakan lebih rendah untuk perbandingan karena masalah Turki I . Sayangnya penanganan Python untuk Is Turki tidak baik. ı dikonversi menjadi I, tetapi saya tidak dikonversi ke ı. İ dikonversi ke i, tetapi saya tidak dikonversi ke İ. 

5
Douglas Leeder

Tidak ada built in yang setara dengan fungsi yang Anda inginkan.

Anda dapat menulis fungsi Anda sendiri yang mengonversi ke .lower () setiap karakter sekaligus untuk menghindari duplikasi kedua string, tetapi saya yakin itu akan sangat intensif dan sangat tidak efisien. 

Kecuali jika Anda bekerja dengan string yang sangat panjang (begitu lama yang dapat menyebabkan masalah memori jika digandakan) maka saya akan membuatnya tetap sederhana dan menggunakan 

str1.lower() == str2.lower()

Kamu akan baik-baik saja

3
Ricardo Reyes

Pertanyaan ini menanyakan 2 hal yang sangat berbeda:

  1. Apa cara termudah untuk membandingkan string dengan Python, mengabaikan case?
  2. Saya kira saya sedang mencari yang setara dengan stricmp ().

Karena # 1 sudah dijawab dengan sangat baik (yaitu: str1.lower () <str2.lower ()) saya akan menjawab # 2.

def strincmp(str1, str2, numchars=None):
    result = 0
    len1 = len(str1)
    len2 = len(str2)
    if numchars is not None:
        minlen = min(len1,len2,numchars)
    else:
        minlen = min(len1,len2)
    #end if
    orda = ord('a')
    ordz = ord('z')

    i = 0
    while i < minlen and 0 == result:
        ord1 = ord(str1[i])
        ord2 = ord(str2[i])
        if ord1 >= orda and ord1 <= ordz:
            ord1 = ord1-32
        #end if
        if ord2 >= orda and ord2 <= ordz:
            ord2 = ord2-32
        #end if
        result = cmp(ord1, ord2)
        i += 1
    #end while

    if 0 == result and minlen != numchars:
        if len1 < len2:
            result = -1
        Elif len2 < len1:
            result = 1
        #end if
    #end if

    return result
#end def

Hanya gunakan fungsi ini ketika masuk akal karena dalam banyak kasus teknik huruf kecil akan lebih unggul.

Saya hanya bekerja dengan string ascii, saya tidak yakin bagaimana ini akan berperilaku dengan unicode.

2
trevorcroft

Ketika sesuatu tidak didukung dengan baik di perpustakaan standar, saya selalu mencari paket PyPI. Dengan virtualisasi dan di mana-mana distribusi Linux modern, saya tidak lagi menghindari ekstensi Python. PyICU tampaknya sesuai dengan tagihan: https://stackoverflow.com/a/1098160/3461

Sekarang ada juga pilihan yaitu python murni. Telah teruji dengan baik: https://github.com/jtauber/pyuca


Jawaban lama:

Saya suka solusi ekspresi reguler. Berikut adalah fungsi yang dapat Anda salin dan tempel ke fungsi apa pun, berkat dukungan struktur blok python.

def equals_ignore_case(str1, str2):
    import re
    return re.match(re.escape(str1) + r'\Z', str2, re.I) is not None

Karena saya menggunakan kecocokan alih-alih pencarian, saya tidak perlu menambahkan tanda sisipan (^) ke ekspresi reguler.

Catatan: Ini hanya memeriksa kesetaraan, yang terkadang dibutuhkan. Saya juga tidak akan mengatakan bahwa saya menyukainya.

2
Benjamin Atkin

Ungkapan yang disarankan untuk mengurutkan daftar nilai menggunakan kunci mahal untuk menghitung adalah dengan apa yang disebut "pola dihiasi". Ini terdiri hanya dalam membangun daftar (kunci, nilai) tupel dari daftar asli, dan mengurutkan daftar itu. Maka sepele untuk menghilangkan kunci dan mendapatkan daftar nilai yang diurutkan:

>>> original_list = ['a', 'b', 'A', 'B']
>>> decorated = [(s.lower(), s) for s in original_list]
>>> decorated.sort()
>>> sorted_list = [s[1] for s in decorated]
>>> sorted_list
['A', 'a', 'B', 'b']

Atau jika Anda suka one-liners:

>>> sorted_list = [s[1] for s in sorted((s.lower(), s) for s in original_list)]
>>> sorted_list
['A', 'a', 'B', 'b']

Jika Anda benar-benar khawatir tentang biaya panggilan yang lebih rendah (), Anda bisa menyimpan tupel (string yang diturunkan, string asli) di mana-mana. Tuples adalah jenis wadah termurah di Python, mereka juga dapat dip has sehingga mereka dapat digunakan sebagai kunci kamus, mengatur anggota, dll.

1
Antoine P.

Ini adalah bagaimana Anda akan melakukannya dengan re:

import re
p = re.compile('^hello$', re.I)
p.match('Hello')
p.match('hello')
p.match('HELLO')
1
Moses Ting

Untuk perbandingan sesekali atau bahkan berulang, beberapa objek string tambahan seharusnya tidak masalah asalkan ini tidak akan terjadi di loop terdalam kode inti Anda atau Anda tidak memiliki cukup data untuk benar-benar melihat dampak kinerja. Lihat apakah Anda melakukannya: melakukan sesuatu dengan cara "bodoh" jauh lebih tidak bodoh jika Anda juga melakukannya lebih sedikit.

Jika Anda benar-benar ingin terus membandingkan banyak dan banyak case-insensitif teks Anda entah bagaimana bisa menjaga versi huruf kecil dari string untuk menghindari finalisasi dan penciptaan ulang, atau menormalkan seluruh data yang ditetapkan menjadi huruf kecil. Ini tentu saja tergantung pada ukuran set data. Jika ada jarum yang relatif sedikit dan tumpukan jerami besar, mengganti jarum dengan objek regexp yang dikompilasi adalah salah satu solusi. Jika sulit dikatakan tanpa melihat contoh nyata.

0
yason

Anda bisa menerjemahkan setiap string menjadi huruf kecil sekali --- malas hanya saat Anda membutuhkannya, atau sebagai persiapan untuk mengurutkannya jika Anda tahu Anda akan mengurutkan seluruh koleksi string. Ada beberapa cara untuk melampirkan kunci perbandingan ini ke data aktual yang sedang disortir, tetapi teknik ini harus ditangani dalam masalah terpisah.

Perhatikan bahwa teknik ini dapat digunakan tidak hanya untuk menangani masalah huruf besar/kecil, tetapi untuk jenis penyortiran lainnya seperti penyortiran spesifik lokal, atau penyortiran judul "Gaya perpustakaan" yang mengabaikan artikel terkemuka dan sebaliknya menormalkan data sebelum menyortirnya.

0
Dale Wilson

Cukup gunakan metode str().lower(), kecuali kinerja tinggi penting - dalam hal ini tulis metode penyortiran itu sebagai ekstensi C.

"Cara menulis Ekstensi Python" sepertinya intro yang layak ..

Lebih menarik lagi, Panduan ini membandingkan dengan menggunakan ctypes library vs menulis modul C eksternal (ctype jauh lebih lambat dari ekstensi C).

0
dbr
import re
if re.match('tEXT', 'text', re.IGNORECASE):
    # is True
0
Venkatesh Bachu

Saya cukup yakin Anda harus menggunakan .lower () atau menggunakan ekspresi reguler. Saya tidak mengetahui adanya fungsi perbandingan string bawaan yang tidak sensitif.

0
Mark Biek