51 votes

Ignorer la casse dans les chaînes de caractères Python

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

Bien sûr, on peut faire (str1.lower() <= str2.lower()), etc., mais cela crée deux chaînes temporaires supplémentaires (avec les surcoûts évidents d'allocation/de gestion).

Je suppose que je cherche un équivalent de stricmp() en C.

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

Supposons que vous vouliez trier une longue liste de chaînes de caractères. Il suffit de faire theList.sort(). Cela représente O(n * log(n)) comparaisons de chaînes et aucune gestion de la mémoire (puisque toutes les chaînes et tous les éléments de la liste sont des pointeurs intelligents). chaînes de caractères et les éléments de la liste sont des sortes de pointeurs intelligents). Vous êtes heureux.

Maintenant, vous voulez faire la même chose, mais en ignorant la casse (simplifions et disons que toutes les chaînes de caractères sont en ascii, ce qui permet d'ignorer les problèmes de locale). Vous pouvez faire theList.sort(key=lambda s : s.lower()), mais alors vous provoquez deux nouvelles deux nouvelles allocations par comparaison, en plus de charger le collecteur de déchets avec les chaînes (abaissées) dupliquées. Chacun de ces bruits de gestion de la mémoire est plus lent que la simple comparaison de chaînes de caractères.

Maintenant, avec une fonction in-place de type stricmp(), vous pouvez faire : theList.sort(cmp=stricmp) et c'est aussi rapide et aussi facile à mémoriser que theList.sort(). Vous êtes à nouveau heureux.

Le problème est que toute comparaison insensible à la casse basée sur Python implique une chaîne de caractères implicite. donc je m'attendais à trouver une comparaison basée sur le C (peut-être dans le module string).

Je n'ai rien trouvé de tel, d'où ma question ici. (J'espère que cela clarifie la question).

0 votes

Php Equivalent : strcasecmp - nl3.php.net/strcasecmp

4 votes

Vos hypothèses sont fausses. list.sort() avec une clé= fait pas signifie "deux nouvelles allocations par comparaison". (list.sort avec le cmp=, d'autre part fait appeler l'argument pour chaque comparaison)

0 votes

A tenté de renommer la question de Ignore case in python strings à What's closest to stricmp in Python for 7-bit ascii string comparison? pour refléter plus précisément la question réelle de l'op. principal problème : l'unicode est aussi une "chaîne", mais cette question leur permettrait d'obtenir une réponse. totalement faux ; voir les commentaires de tchrist

75voto

hop Points 15423

Voici un benchmark 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 typique 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

Ainsi, la version avec str.lower est non seulement la plus rapide de loin, mais aussi la plus portable et la plus pythique de toutes les solutions proposées ici. Je n'ai pas établi de profil d'utilisation de la mémoire, mais l'auteur de l'article original n'a toujours pas donné de raison convaincante de s'en inquiéter. De plus, qui a dit qu'un appel au module libc ne dupliquait aucune chaîne de caractères ?

NB : Le lower() La méthode des cordes a également l'avantage de dépendre de la situation locale. C'est une chose que vous ne ferez probablement pas en écrivant votre propre solution "optimisée". Malgré cela, en raison de bogues et de fonctionnalités manquantes dans Python, ce type de comparaison peut donner des résultats erronés dans un contexte unicode.

3 votes

Bien sûr, la mémoire est un problème, puisque plus de 99,9 % du temps de la fonction .lower() est consacré à l'allocation de mémoire. De plus, sur les machines (Windows) que j'ai vérifiées, l'approche key=_stricmp était 4 à 5 fois plus rapide, et sans contrainte de mémoire.

4 votes

4-5 fois plus rapide que la méthode .lower signifierait qu'elle est 2 fois plus rapide que le cas de tri simple. comment est-ce possible ? !?

0 votes

@hop tous les mots de la liste de mots sur laquelle vous faites le test sont déjà en minuscules. Cela pourrait vous donner des résultats qui sont loin de ceux de Paul.

7voto

tzot Points 32224

Votre question implique que vous n'avez pas besoin d'Unicode. Essayez l'extrait de code suivant ; s'il 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']

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

4 votes

Le paramétrage global de locale.setlocale() est évidemment exagéré (beaucoup trop global).

0 votes

Je ne sais pas ce qu'est la "surcharge évidente", et le paramètre "global" peut être aussi localisé que vous le souhaitez (sauf si vous travaillez avec des fils et que vous avez besoin de certains fils localisés et d'autres non, pour une raison quelconque).

1 votes

C'est la seule solution qui produit des résultats pouvant interagir correctement avec des utilitaires insensibles à la casse tels que Unix sort avec l'option -f. Par exemple, str.lower fait en sorte que A_ soit trié avant AA.

7voto

Eli Courtwright Points 53071

Utilisez-vous cette comparaison dans un chemin très fréquemment exécuté d'une application très sensible aux performances ? Ou bien, exécutez-vous cette comparaison sur des chaînes de caractères qui font des mégaoctets ? Si ce n'est pas le cas, vous ne devez pas vous soucier des performances et vous contenter d'utiliser la méthode .lower().

Le code suivant démontre qu'une comparaison insensible à la casse en appelant .lower() sur deux chaînes de caractères de près d'un mégaoctet chacune prend environ 0,009 seconde sur mon ordinateur de bureau de 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 d'une section de code extrêmement importante et critique en termes de performances, je recommande d'écrire une fonction en C et de l'appeler à partir de votre code Python, car cela vous permettra d'effectuer une recherche insensible à la casse vraiment efficace. Vous trouverez des détails sur l'écriture de modules d'extension en C ici : https://docs.python.org/extending/extending.html

3 votes

C'est donc ainsi que l'on passe des choses à la classe Timer. Merci d'avoir résolu un problème très différent du mien :)

5 votes

C'est complètement faux. Elle ne détecte pas que et sont le même cas insensément. Vous ne devez pas utiliser le casemapping pour comparer la casse dans Unicode. Vous devez utiliser le respect de la casse. Ce sont des choses différentes. , , sont toutes les mêmes, tout comme S, , s (c'est quoi le problème avec les s de toute façon ? :) et , , µ sont. Il y a d'innombrables autres circonstances similaires, comme par exemple comment weiß, WEI, weiss, WEISS sont toutes les mêmes aussi, ou ecient, efficace. Vous doivent utiliser des casse-tête, parce que les casemates ne fonctionnent pas.

6voto

Douglas Leeder Points 29986

Je ne trouve pas d'autre moyen intégré de faire une comparaison insensible à la casse : Le site recette du livre de cuisine python utilise lower().

Cependant, vous devez être prudent lorsque vous utilisez les valeurs inférieures pour les comparaisons, en raison de l'influence de l'indice d'erreur de l'UE. Problème de turc I . Malheureusement, la gestion du turc Is par Python n'est pas bonne. est converti en I, mais I n'est pas converti en . est converti en i, mais i n'est pas converti en .

4 votes

Python ne gère pas l'Unicode de manière très robuste, comme vous avez pu le constater. Les casemaps ne font pas attention à ces choses. Très triste.

3voto

Ricardo Reyes Points 3428

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

Vous pouvez écrire votre propre fonction qui convertit en .lower() chaque caractère à la fois pour éviter de dupliquer les deux chaînes, mais je suis sûr qu'elle sera très gourmande en ressources informatiques et extrêmement inefficace.

A moins que vous ne travailliez avec des chaînes de caractères extrêmement longues (si longues qu'elles peuvent causer un problème de mémoire si elles sont dupliquées), je préfère rester simple et utiliser

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

Tu vas t'en sortir.

2 votes

"Il ne faut jamais dire jamais" :) "Il n'y a pas d'équivalent intégré" est absolu ; "Je ne connais pas d'équivalent intégré" serait plus proche de la vérité. locale.strcoll, étant donné un LC_COLLATE insensible à la casse (comme 'en_US'), est un intégré.

2 votes

Cette réponse est fausse. La seule façon correcte est str1.fold() == str2.fold() mais cela nécessite une extension de la classe de chaînes de caractères par défaut de python qui prend en charge la casse complète d'une chaîne de caractères en Unicode. C'est une fonction manquante.

0 votes

@tchrist unclearr : une telle extension est-elle disponible ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X