2008-09-15 159 views
50

¿Cuál es la forma más fácil de comparar cadenas en Python, ignorando mayúsculas y minúsculas?Ignorar mayúsculas y minúsculas en Python Strings

Por supuesto que se puede hacer (str1.lower() < = str2.lower()), etc., pero esto creó dos cadenas temporales adicionales (con los obvios gastos generales de alloc/g-c).

Supongo que estoy buscando un equivalente a C stricmp().

[Algunos contexto más solicitados, por lo que voy a demostrar con un ejemplo trivial:]

Supongamos que desea ordenar una lista de cadenas looong. Simplemente haces theList.sort(). Esto es O (n * log (n)) comparaciones de cadenas y ninguna gestión de memoria (ya que todas las cadenas y los elementos de lista son algún tipo de punteros inteligentes). Usted es feliz.

Ahora, quiere hacer lo mismo, pero ignore el caso (simplifiquemos y digamos todas las cadenas son ascii, por lo que los problemas de configuración regional se pueden ignorar). Puede hacer theList.sort (clave = lambda s: s.lower()), pero luego ocasiona dos nuevas asignaciones por comparación, además de cargar al recolector de basura con las cadenas duplicadas (bajado). Cada uno de esos ruidos de gestión de memoria es de órdenes de magnitud más lentos que la simple comparación de cadenas.

Ahora, con una función similar a stricmp in situ(), lo hace: theList.sort (cmp = stricmp) y es tan rápido y tan fácil de usar como theList.sort(). Eres feliz de nuevo.

El problema es que cualquier comparación insensible a mayúsculas o minúsculas basada en Python implica implícita cadena duplicaciones, por lo que esperaba encontrar una comparación basada en C (tal vez en cadena de módulo).

No se pudo encontrar nada de eso, de ahí la pregunta aquí. (Espero que esto aclare la pregunta).

+0

PHP equivalente: strcasecmp - http://nl3.php.net/strcasecmp – fijter

+4

sus suposiciones son incorrectas. list.sort() con una clave = does _not_ significa "dos nuevas asignaciones por comparación". (list.sort con cmp =, por otro lado _does_ llama al argumento para cada comparación) – hop

+0

intentado cambiar el nombre de la pregunta de 'Ignorar caso en cadenas de python' a' Lo más cercano a stricmp en Python para comparación de cadena ascii de 7 bits ? 'para reflejar con mayor precisión la pregunta real de la operación. problema principal: unicode también es 'cadena', pero esta pregunta los haría * totalmente * incorrectos; ver los comentarios de tchrist – n611x007

Respuesta

-11

En respuesta a su aclaración ...

Usted podría utilizar ctypes para ejecutar la función c "strcasecmp". Ctypes está incluido en Python 2.5. Proporciona la capacidad de llamar a dll y bibliotecas compartidas como libc. Aquí está un ejemplo rápido (Python en Linux; véase el enlace para Win32 ayuda):

from ctypes import * 
libc = CDLL("libc.so.6") // see link above for Win32 help 
libc.strcasecmp("THIS", "this") // returns 0 
libc.strcasecmp("THIS", "THAT") // returns 8 

también pueden querer hacer referencia a strcasecmp documentation

realmente No estoy seguro es más rápido o más lento (no lo he probado), pero es una forma de usar una función C para hacer comparaciones de cadenas insensibles a mayúsculas y minúsculas.

~~~~~~~~~~~~~~

ActiveState Code - Recipe 194371: Case Insensitive Strings es una receta para la creación de una clase de cadena de mayúsculas y minúsculas.Puede ser un poco exagerado para algo rápido, pero podría proporcionarle una forma común de manejar cadenas insensibles a mayúsculas y minúsculas si planea usarlas con frecuencia.

+0

conozco bien esta receta, pero detrás de escena solo tiene un duplicado en minúscula para cada cadena, lo cual no es bueno (como se explica en el ejemplo trivial I agregado) –

+0

La solución ctype es lo que estaba buscando, gracias. Para referencia, aquí está el código Win32: de ctypes import * clib = cdll.LoadLibrary ("msvcrt") theList = [ "abc", "ABC", "def", "DEF"] * 1000000 theList .sort (cmp = clib._stricmp) –

+2

esto es mucho más lento. ver mi respuesta! – hop

0

Estoy bastante seguro de que o bien tienes que usar .lower() o usar una expresión regular. No conozco una función de comparación de cadenas insensible a mayúsculas/minúsculas incorporada.

1

Esta es la forma en que lo harías con re:

import re 
p = re.compile('^hello$', re.I) 
p.match('Hello') 
p.match('hello') 
p.match('HELLO') 
+0

Las expresiones regulares que no distinguen entre mayúsculas y minúsculas solo se pueden usar para pruebas de igualdad (Verdadero/False), no comparación (menor que/igual/mayor que) – tzot

-1

Usted podría subclase str y crear su propia clase string caso insenstive pero en mi humilde opinión de que sería extremadamente imprudente y crear muchos más problemas de lo que vale .

0

Para comparaciones ocasionales o incluso repetidas, algunos objetos de cadena adicionales no deberían importar siempre que esto no ocurra en el ciclo interno de su código central o no tenga datos suficientes para notar realmente el impacto en el rendimiento . Vea si lo hace: hacer las cosas de una manera "estúpida" es mucho menos estúpido si también lo hace menos.

Si realmente quiere seguir comparando montones y montones de texto sin distinguir entre mayúsculas y minúsculas, podría mantener las versiones en minúscula de las cadenas a mano para evitar la finalización y la recreación, o normalizar todo el conjunto de datos en minúsculas. Esto, por supuesto, depende del tamaño del conjunto de datos. Si hay relativamente pocas agujas y un gran pajar, reemplazar las agujas con objetos regexp compilados es una solución. Si es difícil de decir sin ver un ejemplo concreto.

3

No hay una función incorporada equivalente a la función que desea.

Puede escribir su propia función que convierta en .lower() cada carácter a la vez para evitar la duplicación de ambas cadenas, pero estoy seguro de que requerirá mucha CPU y será extremadamente ineficiente.

menos que esté trabajando con cadenas extremadamente largas (siempre que puede causar un problema de memoria si se duplica) entonces yo que sea sencillo y utilizar

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

todo irá bien

+1

"Nunca digas nunca" :) "No hay un equivalente incorporado" es absoluto; "No sé de ningún equivalente incorporado" estaría más cerca de la verdad. locale.strcoll, dado que LC_COLLATE insensible a mayúsculas y minúsculas (como 'en_US' es), está incorporado. – tzot

+2

Esta respuesta es incorrecta. La única forma correcta es 'str1.fold() == str2.fold()', pero eso requiere una extensión a la clase de cadena de python predeterminada que admita el doblez de Unicode completo de una cadena. Es una función que falta. – tchrist

+0

@tchrist unclearr: ¿hay una extensión disponible? – n611x007

7

¿Estás usando esta comparación en una ruta muy frecuentemente ejecutada de una aplicación altamente sensible al rendimiento? Alternativamente, ¿está ejecutando esto en cadenas que tienen megabytes de tamaño? De lo contrario, no debe preocuparse por el rendimiento y simplemente utilizar el método .lower().

El código siguiente muestra que el hacer un caso-insensibles comparar llamando .lower() en dos cadenas que son cada uno casi un megabyte de tamaño tarda unos 0,009 segundos en la misma computadora 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 

Si de hecho esta es una sección de código extremadamente importante y crítica para el rendimiento, entonces recomiendo escribir una función en C y llamarla desde su código Python, ya que eso le permitirá hacer una búsqueda verdaderamente eficiente y que no distinga entre mayúsculas y minúsculas. Detalles sobre cómo escribir los módulos de extensión C se pueden encontrar aquí: https://docs.python.org/extending/extending.html

+2

así es como pasas cosas a la clase Timer. gracias por resolver un picor muy diferente al mío :) – Manav

+5

Esto es completamente incorrecto. No puede detectar que * ΣΤΙΓΜΑΣ * y * στιγμας * sean el mismo caso de manera insensata. No debe usar casemapping para comparar mayúsculas y minúsculas en Unicode. Debe usar plegado de plegado. Estas son cosas diferentes. * Σ, σ, ς * son todos iguales, igual que * S, s, s * (¿qué es con s de todos modos? :) y * Μ, μ, μ * son. Incontables otras circunstancias similares, como * weiß, WEIẞ, weiss, WEISS * son todas iguales, o * e fi cientes, eficientes. * Usted ** debe usar pliegues, ** porque los mapas de casos no funcionan. – tchrist

4

No puedo encontrar ninguna otra forma incorporada de hacer una comparación insensible a las mayúsculas y minúsculas: El python cook-book recipe usa lower().

Sin embargo, debe tener cuidado al usar el más bajo para las comparaciones debido al Turkish I problem. Desafortunadamente, el manejo de Python para Turkish Is no es bueno. ı se convierte en I, pero I no se convierte en ı. © se convierte a i, pero i no se convierte a İ.

+4

Python no maneja Unicode de manera muy robusta, como habrás visto. Los mapas de casos no prestan atención a estas cosas. Muy triste. – tchrist

0

Puede traducir cada cadena a minúsculas una vez --- perezosamente solo cuando lo necesite, o como una preparación previa si sabe que va a ordenar toda la colección de cadenas. Hay varias formas de adjuntar esta clave de comparación a los datos reales que se están ordenando, pero estas técnicas se deben abordar en un tema aparte.

Tenga en cuenta que esta técnica se puede utilizar no solo para resolver problemas de mayúscula/minúscula, sino también para otros tipos de ordenación como ordenamiento local específico o clasificación de título "tipo biblioteca" que ignora los artículos principales y normaliza los datos antes de ordenarlo

+0

la pregunta es más general que el ejemplo en sí (en realidad, en escenarios de la vida real no desea que le moleste adjuntar una versión en minúscula a cada cadena que pueda necesitar icmp() más adelante), pero incluso en este ejemplo trivial, no quiero duplicar la memoria solo para poder ordenar ... –

1

La expresión idiomática recomendada para ordenar listas de valores usando claves costosas para calcular es para el llamado "patrón decorado". Consiste simplemente en crear una lista de tuplas (clave, valor) de la lista original y ordenar esa lista. Entonces es trivial para eliminar las llaves y obtener la lista de valores ordenados:

>>> 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 si se quiere de una sola línea:

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

Si realmente se preocupe por el costo de llamar inferior() , puedes simplemente almacenar tuplas de (cuerda baja, cuerda original) en todas partes. Las tuplas son el tipo más barato de contenedores en Python, también son lavables, por lo que se pueden usar como claves de diccionario, establecer miembros, etc.

+0

tupples son baratos, pero la duplicación de cadenas no es ... –

+2

esto también es lo que hace python con la clave = argumento. – hop

+1

Esta es una mentalidad de 7 bits que es totalmente inapropiada para los datos Unicode. Debe utilizar el doblete de página Unicode completo, o bien la intensidad de compaginación principal según el Algoritmo de intercalación Unicode. Sí, eso significa nuevas copias de la cadena de cualquier manera, pero al menos entonces puedes hacer una comparación binaria en lugar de tener que hurgar en las tablas para cada punto de código. – tchrist

7

Su pregunta implica que no necesita Unicode. Pruebe el siguiente fragmento de código; si funciona para usted, ya está hecho:

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

Aclaración: en caso de que no es evidente a primera vista, parece ser locale.strcoll la función que necesita, evitando la str.lower o locale.strxfrm cadenas "duplicadas"

+3

La configuración global de locale.setlocale() es obviamente una exageración (demasiado global). –

+0

No sé cuál es la "obviedad obvia", y la configuración "global" puede ser tan localizada como desee (excepto si trabaja con subprocesos y necesita algunos subprocesos localizados y otros no, por alguna razón). – tzot

+1

Esta es la única solución que produce resultados que pueden interactuar correctamente con utilidades que no distinguen entre mayúsculas y minúsculas, como Unix sort con la opción -f. Por ejemplo, str.lower hace que A_ ordene antes de AA. –

73

que aquí hay una referencia que muestra que el uso de str.lower es más rápido que el método de la respuesta aceptada propuesto (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)) 

tiempos típicos en mi máquina:

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 

Por lo tanto, la versión con str.lower no es solo la más rápida, sino también la más portátil y pitónica de todas las soluciones propuestas aquí. No he perfilado el uso de la memoria, pero el póster original aún no ha dado una razón convincente para preocuparse por ello. Además, ¿quién dice que una llamada al módulo libc no duplica cadenas?

NB: El método de cadena lower() también tiene la ventaja de ser dependiente de la configuración regional. Algo que probablemente no estarás acertando al escribir tu propia solución "optimizada". Aun así, debido a errores y funciones faltantes en Python, este tipo de comparación puede arrojar resultados erróneos en un contexto Unicode.

+2

Por supuesto, la memoria es un problema, ya que más del 99.9% del tiempo .lower() es la asignación de memoria. Además, en las máquinas (de Windows) que revisé, el método key = _stricmp era 4-5 veces más rápido y sin memoria. –

+4

4-5 veces más rápido que el .lower-method significa que es 2 veces más rápido que el simple caso de clasificación. ¿¡¿como puede ser?!? – hop

+0

@hop todas las palabras en la lista de palabras que prueba ya están en minúscula. Eso podría darte resultados que están lejos de los de Pablo. –

0

Simplemente utilice el método str().lower(), a menos que el alto rendimiento sea importante, en cuyo caso escriba ese método de clasificación como una extensión C.

"How to write a Python Extension" parece una introducción decente ..

Más interesante, This guide compara el uso de la biblioteca ctypes vs escribir un módulo C externa (la ctype es bastante sustancialmente más lento que la extensión C).

2

Cuando algo no se admite bien en la biblioteca estándar, siempre busco un paquete PyPI. Con la virtualización y la ubicuidad de las distribuciones Linux modernas, ya no evito las extensiones de Python. PyICU parece ajustarse a la ley: https://stackoverflow.com/a/1098160/3461

Ahora también hay una opción que es python puro.Está bien probado: https://github.com/jtauber/pyuca


vieja respuesta:

me gusta la solución de expresiones regulares. Aquí hay una función que puede copiar y pegar en cualquier función, gracias al soporte de estructura de bloques de python.

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

Dado que he usado match en lugar de buscar, no he tenido que añadir un signo de intercalación (^) a la expresión regular.

Nota: Esto solo comprueba la igualdad, que a veces es lo que se necesita. Tampoco llegaría tan lejos como para decir que me gusta.

+0

[Ojalá hubiera un sello de goma virtual para esto] No use '$', use '\ Z'. Lea el fantástico manual para descubrir lo que '$' realmente hace; no confíes en la leyenda o las conjeturas o lo que sea. –

+0

Lo cambié. También activé la función wiki de la comunidad para mi respuesta. Gracias. –

+0

buena sólo para las pruebas de igualdad, que no es tan absolutamente lo mismo que comparar dos cadenas y determinar si una es-menor que, igual a, o mayor que la otra. – martineau

2

Esta pregunta está pidiendo 2 cosas muy diferentes:

  1. Cuál es la forma más fácil de comparar las cadenas en Python, ignorando mayúsculas y minúsculas?
  2. Supongo que estoy buscando un equivalente a C stricmp().

Dado que # 1 ha sido respondido muy bien ya (es decir: str1.lower() < str2.lower()) Voy a responder # 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 

Solo use esta función cuando tenga sentido ya que en muchos casos la técnica en minúsculas será superior.

Solo trabajo con ascii strings, no estoy seguro de cómo se comportará con unicode.

0
import re 
if re.match('tEXT', 'text', re.IGNORECASE): 
    # is True 
Cuestiones relacionadas