it-swarm.it

Ignora caso in stringhe Python

Qual è il modo più semplice per confrontare le stringhe in Python, ignorando il caso?

Ovviamente si può fare (str1.lower () <= str2.lower ()), ecc., Ma questo ha creato due stringhe temporanee aggiuntive (con ovvi allocazioni alloc/g-c).

Suppongo che sto cercando un equivalente a C's stricmp ().

[Qualche altro contesto richiesto, quindi dimostrerò con un esempio banale:]

Supponiamo di voler ordinare una lista di stringhe. Basta fareList.sort () . Questo è O (n * log (n)) confronti tra stringhe e nessuna gestione della memoria (poiché tutte le stringhe E gli elementi di lista sono una sorta di puntatori intelligenti). Tu sei felice.

Ora, vuoi fare lo stesso, ma ignorare il caso (cerchiamo di semplificare e dire Tutte le stringhe sono ascii, quindi i problemi delle impostazioni locali possono essere ignorati) . Puoi fare theList.sort (key = lambda s: s .lower ()), ma poi si creano due nuove allocazioni per confronto, oltre a gravare sul garbage collector con le stringhe duplicate (ridotte). .__ Ciascuno di questi disturbi di gestione della memoria è più lento degli ordini di grandezza rispetto al semplice confronto tra stringhe.

Ora, con una funzione di tipo stricmp () sul posto, si esegue: theList.sort (cmp = stricmp) Ed è veloce e facile da ricordare come theList.sort (). Sei di nuovo felice.

Il problema è che qualsiasi confronto senza distinzione tra maiuscole e minuscole basato su Python implica duplicazioni di stringhe Implicite, quindi mi aspettavo di trovare un confronto basato su C (forse nella stringa del modulo).

Non ho trovato nulla del genere, quindi la domanda qui . (Spero che questo chiarisca la domanda).

51
Paul Oyster

Ecco un benchmark che mostra che usare str.lower è più veloce del metodo proposto dalla risposta accettata (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))

tempi tipici sulla mia macchina:

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

Quindi, la versione con str.lower non è solo la più veloce di gran lunga, ma anche la più portabile e Python di tutte le soluzioni proposte qui Non ho profilato l'uso della memoria, ma il poster originale non ha ancora dato un motivo convincente per preoccuparsene Inoltre, chi dice che una chiamata nel modulo libc non duplica alcuna stringa?

NB: Il metodo stringa lower() ha anche il vantaggio di essere dipendente dalla locale. Qualcosa che probabilmente non starai giusto scrivendo la tua soluzione "ottimizzata". Anche così, a causa di bug e funzionalità mancanti in Python, questo tipo di confronto può dare risultati errati in un contesto unicode.

74
user3850

Stai utilizzando questo confronto in un percorso eseguito con molta frequenza di un'applicazione sensibile alle prestazioni? In alternativa, stai eseguendo questo su stringhe che hanno dimensioni di megabyte? In caso contrario, non dovresti preoccuparti delle prestazioni e utilizzare semplicemente il metodo .lower ().

Il seguente codice dimostra che eseguendo un confronto senza distinzione tra maiuscole e minuscole, chiamando .lower () su due stringhe di dimensioni pari a quasi un megabyte, impiega circa 0,009 secondi sul mio computer desktop a 1,8 GHz:

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

Se in effetti questa è una sezione di codice estremamente significativa e performante per le prestazioni, allora ti consiglio di scrivere una funzione in C e di chiamarla dal tuo codice Python, poiché ciò ti consentirà di fare una ricerca veramente insensibile alle maiuscole e minuscole. I dettagli sulla scrittura dei moduli di estensione C possono essere trovati qui: https://docs.python.org/extending/extending.html

7
Eli Courtwright

La tua domanda implica che non hai bisogno di Unicode. Prova il seguente snippet di codice; se funziona per te, hai finito:

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

Chiarimento: nel caso non sia ovvio a prima vista, locale.strcoll sembra essere la funzione di cui hai bisogno, evitando le stringhe "duplicate" str.lower o locale.strxfrm.

7
tzot

Non riesco a trovare nessun altro modo integrato di fare confronti case-insensitive: la ricetta python cook-book usa lower ().

Tuttavia devi stare attento quando usi lower per i confronti a causa del problema Turkish I . Sfortunatamente la gestione di Python per Turkish Is non è buona. ı viene convertito in I, ma non viene convertito in ı. © è convertito in i, ma non viene convertito in ©. 

5
Douglas Leeder

Non esiste un equivalente integrato a quella funzione che si desidera.

Puoi scrivere la tua funzione che converte in .lower () ogni carattere alla volta per evitare di duplicare entrambe le stringhe, ma sono sicuro che sarà molto intensivo in termini di CPU ed estremamente inefficiente. 

A meno che tu non stia lavorando con stringhe estremamente lunghe (così a lungo che possono causare problemi di memoria se duplicate), allora lo terrei semplice e userei 

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

Starai bene

3
Ricardo Reyes

Questa domanda sta chiedendo 2 cose molto diverse:

  1. Qual è il modo più semplice per confrontare le stringhe in Python, ignorando il caso?
  2. Suppongo che sto cercando un equivalente a C's stricmp ().

Dal momento che il # 1 ha già risposto molto bene (es: str1.lower () <str2.lower ()) Risponderò al n.

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

Utilizzare questa funzione solo quando ha senso, poiché in molti casi la tecnica minuscola sarà superiore.

Io lavoro solo con le stringhe ascii, non sono sicuro di come si comporterà con unicode.

2
trevorcroft

Quando qualcosa non è supportato bene nella libreria standard, cerco sempre un pacchetto PyPI. Con la virtualizzazione e l'ubiquità delle moderne distribuzioni Linux, non evito più le estensioni Python. PyICU sembra adattarsi alla proposta: https://stackoverflow.com/a/1098160/3461

Ora c'è anche un'opzione che è puro python. È ben testato: https://github.com/jtauber/pyuca


Vecchia risposta:

Mi piace la soluzione di espressione regolare. Ecco una funzione che puoi copiare e incollare in qualsiasi funzione, grazie al supporto della struttura a blocchi di Python.

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

Poiché ho usato la corrispondenza anziché la ricerca, non ho bisogno di aggiungere un segno di omissione (^) all'espressione regolare.

Nota: Questo controlla solo l'uguaglianza, che a volte è ciò che è necessario. Inoltre, non mi spingerei così lontano da dire che mi piace.

2
Benjamin Atkin

L'idioma consigliato per ordinare elenchi di valori usando chiavi costose per il calcolo è il cosiddetto "motivo decorato". Consiste semplicemente nella creazione di un elenco di tuple (chiave, valore) dall'elenco originale e l'ordinamento. Quindi è banale eliminare le chiavi e ottenere l'elenco dei valori ordinati:

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

O se ti piacciono i one-liner:

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

Se ti preoccupi veramente del costo di chiamare lower (), puoi semplicemente memorizzare tuple di (stringa abbassata, stringa originale) ovunque. Le tuple sono il tipo di container più economico in Python, sono anche lavabili in modo che possano essere utilizzate come chiavi del dizionario, set di membri, ecc.

1
Antoine P.

Questo è come lo faresti con re:

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

Per confronti occasionali o addirittura ripetuti, alcuni oggetti stringa aggiuntivi non dovrebbero importare fintanto che questo non si verificherà nel ciclo più interno del codice base o non si dispone di dati sufficienti per notare effettivamente l'impatto sulle prestazioni. Vedi se lo fai: fare le cose in modo "stupido" è molto meno stupido se lo fai anche meno.

Se si vuole seriamente confrontare molti casi di testo con insensibilità, è possibile in qualche modo mantenere le versioni minuscole delle stringhe a portata di mano per evitare la finalizzazione e la ricreazione, o normalizzare l'intero set di dati in minuscolo. Questo ovviamente dipende dalla dimensione del set di dati. Se ci sono un numero relativamente piccolo di aghi e un grande pagliaio, la sostituzione degli aghi con gli oggetti regexp compilati è una soluzione. Se è difficile da dire senza vedere un esempio concreto.

0
yason

Puoi tradurre ogni stringa in minuscolo una volta --- pigramente solo quando ne hai bisogno, o come prepass per l'ordinamento se sai che starai ordinando l'intera collezione di stringhe. Esistono diversi modi per collegare questa chiave di confronto ai dati effettivi ordinati, ma queste tecniche dovrebbero essere affrontate in un problema separato.

Si noti che questa tecnica può essere utilizzata non solo per gestire problemi di maiuscole/minuscole, ma anche per altri tipi di ordinamento come l'ordinamento specifico della locale o ordinamento del titolo "Stile libreria" che ignora gli articoli principali e normalizza in altro modo i dati prima di ordinarli.

0
Dale Wilson

Basta usare il metodo str().lower(), a meno che le alte prestazioni non siano importanti - nel qual caso scrivi quel metodo di ordinamento come un'estensione C.

"Come scrivere un'estensione Python" sembra una decente intro ..

Più interessante, Questa guida confronta usando la libreria ctypes vs scrivendo un modulo C esterno (il ctype è abbastanza sostanzialmente più lento dell'estensione C).

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

Sono abbastanza sicuro che tu debba usare .lower () o usare un'espressione regolare. Non sono a conoscenza di una funzione di confronto stringhe insensibile alle maiuscole e minuscole.

0
Mark Biek