it-swarm-es.tech

Ignorar mayúsculas y minúsculas en cuerdas de Python

¿Cuál es la forma más fácil de comparar cadenas en Python, ignorando el caso?

Por supuesto, uno puede hacer (str1.lower () <= str2.lower ()), etc., pero esto creó dos cadenas temporales adicionales (con las sobrecargas obvias de alloc/g-c).

Supongo que estoy buscando un equivalente a la restricción de C ().

[Se solicita un poco más de contexto, así que lo demostraré con un ejemplo trivial:]

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

Ahora, desea hacer lo mismo, pero ignore el caso (simplifiquemos y digamos que todas las cadenas son ascii, por lo que los problemas de configuración regional pueden ignorarse). Puede hacer theList.sort (key = lambda s: s.lower ()), pero luego causa dos nuevas asignaciones por comparación, más la carga del recolector de basura con las cadenas duplicadas (rebajadas). Cada uno de estos ruidos de administración de memoria es más lento que la simple comparación de cadenas.

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

El problema es que cualquier comparación sin distinción de mayúsculas y minúsculas basada en Python implica duplicaciones de cadenas implícitas, por lo que esperaba encontrar comparaciones basadas en C (tal vez en una cadena de módulos).

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

51
Paul Oyster

Aquí hay un punto de referencia que muestra que usar str.lower es más rápido que el método propuesto por la respuesta aceptada (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 Pythonic de todas las soluciones propuestas aquí. No he perfilado el uso de la memoria, pero el póster original todavía 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 ninguna cadena?

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 características faltantes en Python, este tipo de comparación puede darte resultados incorrectos en un contexto Unicode.

75
user3850

¿Está utilizando esta comparación en una ruta ejecutada con mucha frecuencia de una aplicación altamente sensible al rendimiento? Alternativamente, ¿está ejecutando esto en cadenas que tienen un tamaño de megabytes? Si no es así, no debe preocuparse por el rendimiento y simplemente usar el método .lower ().

El siguiente código demuestra que hacer una comparación que no distinga mayúsculas de minúsculas al llamar a .lower () en dos cadenas que tienen casi un megabyte de tamaño toma aproximadamente 0.009 segundos en mi computadora de escritorio de 1.8GHz:

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 del código extremadamente importante y crítica para el rendimiento, entonces recomiendo escribir una función en C y llamarla desde tu código Python, ya que eso te permitirá realizar una búsqueda verdaderamente eficiente que no tenga en cuenta las mayúsculas. Los detalles sobre cómo escribir los módulos de extensión C se pueden encontrar aquí: https://docs.python.org/extending/extending.html

7
Eli Courtwright

Tu pregunta implica que no necesitas Unicode. Pruebe el siguiente fragmento de código; Si te funciona, ya has terminado:

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 sea obvio a primera vista, locale.strcoll parece ser la función que necesita, evitando las cadenas "duplicadas" de str.lower o locale.strxfrm.

7
tzot

No puedo encontrar ninguna otra forma integrada de hacer una comparación que no distinga entre mayúsculas y minúsculas: La receta de libro de cocina python usa lower ().

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

5
Douglas Leeder

No hay construido en equivalente a la función que desea.

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

A menos que esté trabajando con cadenas extremadamente largas (el tiempo que puede causar un problema de memoria si se duplica), lo mantendré simple y usar.

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

Estarás bien

3
Ricardo Reyes

Esta pregunta hace 2 cosas muy diferentes:

  1. ¿Cuál es la forma más fácil de comparar cadenas en Python, ignorando el caso?
  2. Supongo que estoy buscando un equivalente a la restricción de C ().

Ya que el # 1 ya ha sido contestado muy bien (ie: str1.lower () <str2.lower ()) contestaré el # 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

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

Solo trabajo con cadenas ASCII, no estoy seguro de cómo se comportará con Unicode.

2
trevorcroft

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

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


Respuesta anterior:

Me gusta la solución de expresión regular. Aquí hay una función que puede copiar y pegar en cualquier función, gracias al soporte de la 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

Como utilicé la coincidencia en lugar de la búsqueda, no tuve que agregar un símbolo de intercalación (^) a la expresión regular.

Nota: Esto solo verifica la igualdad, que a veces es lo que se necesita. Tampoco me atrevería a decir que me gusta.

2
Benjamin Atkin

El idioma recomendado para ordenar las listas de valores mediante el uso de claves caras para calcular es el denominado "patrón decorado". Consiste simplemente en crear una lista de tuplas (clave, valor) de la lista original y ordenar esa lista. Entonces es trivial eliminar las claves 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 te gustan las frases sencillas:

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

Si realmente te preocupas por el costo de llamar a lower (), puedes almacenar tuplas de (cadena reducida, cadena original) en todas partes. Las tuplas son el tipo más barato de contenedores en Python, también son hashable por lo que se pueden usar como claves de diccionario, miembros del conjunto, etc.

1
Antoine P.

Así es como lo harías con re:

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

Para comparaciones ocasionales o incluso repetidas, algunos objetos de cadena adicionales no deberían importar siempre que esto no suceda en el bucle más profundo de su código central o no tenga suficientes datos para notar el impacto en el rendimiento. Mira si lo haces: hacer las cosas de manera "estúpida" es mucho menos estúpido si también lo haces menos.

Si realmente desea seguir comparando lotes y lotes de texto sin distinción entre mayúsculas y minúsculas, de alguna manera podría tener a mano las versiones en minúsculas de las cadenas para evitar la finalización y la recreación, o normalizar todo el conjunto de datos en minúsculas. Por supuesto, esto depende del tamaño del conjunto de datos. Si hay relativamente pocas agujas y un pajar grande, reemplazar las agujas con objetos compilados de expresiones regulares es una solución. Si es difícil decirlo sin ver un ejemplo concreto.

0
yason

Puede traducir cada cadena a minúsculas una vez, perezosamente solo cuando la necesite, o como un prepaso al tipo si sabe que estará clasificando toda la colección de cadenas. Hay varias formas de adjuntar esta clave de comparación a los datos reales que se ordenan, pero estas técnicas deben abordarse en un tema aparte.

Tenga en cuenta que esta técnica se puede utilizar no solo para tratar los problemas en mayúsculas y minúsculas, sino también para otros tipos de clasificación, como la clasificación específica de la configuración regional o la clasificación de títulos "estilo biblioteca" que ignora los artículos principales y, de lo contrario, normaliza los datos antes de clasificarlos.

0
Dale Wilson

Simplemente use 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 en C.

"Cómo escribir una extensión de Python" parece una introducción decente ...

Más interesante aún, Esta guía compara el uso de la biblioteca de ctypes frente a la escritura de un módulo C externo (el ctype es bastante más lento que la extensión C).

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

Estoy bastante seguro de que tienes que usar .lower () o usar una expresión regular. No tengo conocimiento de una función de comparación de cadenas integrada que no distinga mayúsculas y minúsculas.

0
Mark Biek