web-dev-qa-db-fra.com

Ignorer la casse dans les chaînes Python

Quel est le moyen le plus simple de comparer des chaînes en Python, en ignorant la casse?

Bien sûr, on peut faire (str1.lower () <= str2.lower ()), etc., mais cela a créé deux chaînes temporaires supplémentaires (avec les frais généraux évidents alloc/g-c).

Je suppose que je recherche un équivalent à la règle stricmp () de C.

[Un peu plus de contexte est demandé, je vais donc démontrer avec un exemple trivial:]

Supposons que vous souhaitiez trier une longue liste de chaînes. Vous faites simplement theList.sort () . Il s’agit de comparaisons de chaînes O (n * log (n)) et pas de gestion de la mémoire (puisque toutes les chaînes Et les éléments de la liste sont une sorte de pointeur intelligent). Tu es heureux.

Maintenant, vous voulez faire la même chose, mais ignorer la casse (simplifions et disons Toutes les chaînes sont ascii, donc les problèmes de paramètres régionaux peuvent être ignorés) . .lower ()), mais vous créez alors deux nouvelles allocations par comparaison, et vous surchargez le ramasse-miettes avec les chaînes dupliquées (abaissées). Chacun de ces bruits de gestion de la mémoire est un ordre de grandeur plus lent que la simple comparaison de chaînes.

Maintenant, avec une fonction semblable à stricmp (), vous le faites: theList.sort (cmp = strictmp) Et elle est aussi rapide et aussi conviviale en mémoire que theList.sort (). Tu es heureux à nouveau.

Le problème est que toute comparaison insensible à la casse basée sur Python implique des duplications implicites de chaînes C'est pourquoi je m'attendais à trouver des comparaisons basées sur C (peut-être dans une chaîne de modules).

Impossible de trouver quelque chose comme ça, d’où la question ici . (Espérons que cela clarifie la question).

51
Paul Oyster

Voici un point de repère montrant que l'utilisation de str.lower est plus rapide que la méthode proposée par la réponse acceptée (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))

temps typiques sur ma machine:

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

Donc, la version avec str.lower est non seulement la plus rapide de loin, mais aussi la plus portable et la plus pythonique de toutes les solutions proposées ici . t'inquiète pour ça. Aussi, qui dit qu'un appel dans le module libc ne duplique aucune chaîne?

NB: La méthode de chaîne lower() a également l'avantage de dépendre de la localisation. Quelque chose que vous ne réussirez probablement pas à faire lorsque vous écrivez votre propre solution "optimisée". Néanmoins, en raison de bogues et de fonctionnalités manquantes dans Python, ce type de comparaison peut vous donner des résultats erronés dans un contexte unicode.

74
user3850

Utilisez-vous cette comparaison dans un chemin très fréquemment exécuté d'une application sensible aux performances élevées? Sinon, utilisez-vous ceci sur des chaînes d'une taille en mégaoctets? Si ce n'est pas le cas, ne vous inquiétez pas pour les performances et utilisez simplement la méthode .lower ().

Le code suivant montre que faire une comparaison insensible à la casse en appelant .lower () sur deux chaînes pesant chacune environ un mégaoctet prend environ 0,009 seconde sur mon ordinateur de bureau à 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

S'il s'agit bien d'une section de code extrêmement importante et critique en termes de performances, je vous recommande d'écrire une fonction en C et de l'appeler à partir de votre code Python, car cela vous permettra d'effectuer une recherche vraiment efficace, sensible à la casse. Des détails sur l'écriture de modules d'extension C peuvent être trouvés ici: https://docs.python.org/extending/extending.html

7
Eli Courtwright

Votre question implique que vous n’avez pas besoin de l’Unicode. Essayez l'extrait de code suivant. si cela fonctionne pour vous, vous avez terminé:

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

Précision: au cas où cela ne serait pas évident à première vue, locale.strcoll semble être la fonction dont vous avez besoin, en évitant les chaînes str.lower ou locale.strxfrm "dupliquées".

7
tzot

Je ne trouve pas d'autre moyen intégré de faire la comparaison sans distinction de casse: Le recette de livre de cuisine python utilise lower ().

Cependant, vous devez faire attention en utilisant low pour les comparaisons à cause du problème turc I . Malheureusement, le traitement de Python pour l’Irlande n’est pas bon. I est converti en I, mais I n'est pas converti en ı. İ est converti en i, mais i n'est pas converti en İ. 

5
Douglas Leeder

Il n'y a pas d'équivalent intégré à cette fonction que vous voulez.

Vous pouvez écrire votre propre fonction qui convertit chaque caractère à la fois pour éviter la duplication des deux chaînes, mais je suis sûr que cela demandera beaucoup de ressources processeur et sera extrêmement inefficace. 

À moins que vous ne travailliez avec des chaînes extrêmement longues (si longues que cela peut causer un problème de mémoire si elles sont dupliquées), je le garderai simplement et l’utiliserai. 

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

Ça va aller

3
Ricardo Reyes

Cette question pose 2 choses très différentes:

  1. Quel est le moyen le plus simple de comparer des chaînes en Python, en ignorant la casse?
  2. Je suppose que je recherche un équivalent à la règle stricmp () de C.

Depuis que # 1 a déjà été très bien répondu (ie: str1.lower () <str2.lower ()), je répondrai à # 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

N'utilisez cette fonction que lorsque cela s'avère utile, car dans de nombreux cas, la technique des minuscules sera supérieure.

Je travaille uniquement avec des chaînes ASCII, je ne sais pas comment cela se comportera avec Unicode.

2
trevorcroft

Quand quelque chose n'est pas bien supporté dans la bibliothèque standard, je cherche toujours un paquet PyPI. Avec la virtualisation et l'omniprésence des distributions Linux modernes, je n'évite plus les extensions Python. PyICU semble correspondre à la facture: https://stackoverflow.com/a/1098160/3461

Il existe maintenant une option qui est purement python. C'est bien testé: https://github.com/jtauber/pyuca


Ancienne réponse:

J'aime la solution d'expression régulière. Voici une fonction que vous pouvez copier et coller dans n’importe quelle fonction, grâce au support de la structure de bloc de python.

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

Depuis que j'ai utilisé match au lieu de recherche, je n'ai pas eu besoin d'ajouter un caret (^) à l'expression régulière.

Note: Ceci ne vérifie que l'égalité, ce qui est parfois nécessaire. Je n'irais pas jusqu'à dire que ça me plait.

2
Benjamin Atkin

L'idiome recommandé pour trier les listes de valeurs à l'aide de clés coûteuses à calculer concerne le "motif décoré". Cela consiste simplement à construire une liste de nuplets (clé, valeur) à partir de la liste d'origine et à trier cette liste. Il est alors trivial d’éliminer les clés et d’obtenir la liste des valeurs triées:

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

Ou si vous aimez les one-liners:

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

Si vous vous inquiétez vraiment du coût d’appel de lower (), vous pouvez simplement stocker des n-uplets de (chaîne abaissée, chaîne originale) partout. Les tuples sont le type de conteneur le moins cher en Python. Ils sont également faciles à utiliser et peuvent donc être utilisés comme clés de dictionnaire, membres de jeu, etc.

1
Antoine P.

Voici comment vous le feriez avec re:

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

Pour des comparaisons occasionnelles, voire répétées, quelques objets chaîne supplémentaires ne devraient pas être importants tant que cela ne se produira pas dans la boucle la plus interne de votre code ou si vous ne disposez pas de suffisamment de données pour constater l'impact sur les performances. Voyez si vous le faites: faire les choses de manière "stupide" est beaucoup moins stupide si vous le faites aussi moins.

Si vous voulez sérieusement continuer à comparer beaucoup de texte sans tenir compte de la casse, vous pouvez conserver les versions minuscules des chaînes pour éviter la finalisation et la recréation, ou normaliser l'ensemble des données en minuscule. Cela dépend bien sûr de la taille de l'ensemble de données. S'il y a relativement peu d'aiguilles et une grande pile de foin, remplacer les aiguilles par des objets regexp compilés est une solution. Si c'est difficile à dire sans voir un exemple concret.

0
yason

Vous pouvez traduire chaque chaîne en minuscule une fois - paresseusement uniquement lorsque vous en avez besoin, ou en guise de pré-tri pour le tri si vous savez que vous allez trier toute la collection de chaînes. Il existe plusieurs façons d'associer cette clé de comparaison aux données réelles en cours de tri, mais ces techniques doivent être traitées dans un problème séparé.

Notez que cette technique peut être utilisée non seulement pour traiter les problèmes majuscules/minuscules, mais pour d'autres types de tri, tels que le tri spécifique à l'environnement local ou le tri de titres de type "bibliothèque" qui ignore les articles principaux et normalise les données avant de les trier.

0
Dale Wilson

Utilisez simplement la méthode str().lower(), sauf si la performance est importante - dans ce cas, écrivez cette méthode de tri en tant qu'extension C.

"Comment écrire une extension Python" semble être une intro décente ..

Plus intéressant, Ce guide compare l'utilisation de la bibliothèque ctypes à l'écriture d'un module C externe (le type c est beaucoup plus lent que l'extension C).

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

Je suis presque sûr que vous devez utiliser .lower () ou utiliser une expression régulière. Je ne connais pas de fonction de comparaison de chaînes intégrée, insensible à la casse.

0
Mark Biek