web-dev-qa-db-fra.com

La chaîne insensible à la casse commence avec Python

Voici comment je vérifie si mystring commence par une chaîne:

>>> mystring.lower().startswith("he")
True

Le problème est que mystring est très long (milliers de caractères), donc l'opération lower() prend beaucoup de temps.

QUESTION: Existe-t-il un moyen plus efficace?

Ma tentative infructueuse:

>>> import re;
>>> mystring.startswith("he", re.I)
False
40
Nicolas Raoul

Vous pouvez utiliser une expression régulière comme suit:

In [33]: bool(re.match('he', 'Hello', re.I))
Out[33]: True 

In [34]: bool(re.match('el', 'Hello', re.I))
Out[34]: False 

Sur une chaîne de 2 000 caractères, ceci est environ 20 fois plus rapide que lower():

In [38]: s = 'A' * 2000

In [39]: %timeit s.lower().startswith('he')
10000 loops, best of 3: 41.3 us per loop

In [40]: %timeit bool(re.match('el', s, re.I))
100000 loops, best of 3: 2.06 us per loop

Si vous faites correspondre le même préfixe à plusieurs reprises, la pré-compilation de l'expression rationnelle peut faire toute la différence:

In [41]: p = re.compile('he', re.I)

In [42]: %timeit p.match(s)
1000000 loops, best of 3: 351 ns per loop

Pour les préfixes courts, couper le préfixe de la chaîne avant de la convertir en minuscule pourrait être encore plus rapide:

In [43]: %timeit s[:2].lower() == 'he'
1000000 loops, best of 3: 287 ns per loop

Le timing relatif de ces approches dépendra bien sûr de la longueur du préfixe. Sur ma machine, le seuil de rentabilité semble être d’environ six caractères, ce qui correspond au moment où la regex pré-compilée devient la méthode la plus rapide.

Dans mes expériences, vérifier chaque caractère séparément pourrait être encore plus rapide:

In [44]: %timeit (s[0] == 'h' or s[0] == 'H') and (s[1] == 'e' or s[1] == 'E')
1000000 loops, best of 3: 189 ns per loop

Toutefois, cette méthode ne fonctionne que pour les préfixes connus lors de l'écriture du code et ne se prête pas à des préfixes plus longs. 

43
NPE

Que dis-tu de ça:

prefix = 'he'
if myVeryLongStr[:len(prefix)].lower() == prefix.lower()
23
inspectorG4dget

En fonction des performances de .lower (), si le préfixe est suffisamment petit, il peut être plus rapide de vérifier l’égalité plusieurs fois:

s =  'A' * 2000
prefix = 'he'
ch0 = s[0] 
ch1 = s[1]
substr = ch0 == 'h' or ch0 == 'H' and ch1 == 'e' or ch1 == 'E'

Synchronisation (en utilisant la même chaîne que NPE):

>>> timeit.timeit("ch0 = s[0]; ch1 = s[1]; ch0 == 'h' or ch0 == 'H' and ch1 == 'e' or ch1 == 'E'", "s = 'A' * 2000")
0.2509511683747405

= 0.25 us per loop

Par rapport à la méthode existante:

>>> timeit.timeit("s.lower().startswith('he')", "s = 'A' * 2000", number=10000)
0.6162763703208611

= 61.63 us per loop

(C'est horrible, bien sûr, mais si le code est extrêmement critique en termes de performances, alors il pourrait en valoir la peine)

2
Alex L

Aucune des réponses fournies n'est en réalité correcte, dès que vous considérez quelque chose en dehors de la plage ASCII. 

Par exemple, dans une comparaison sans distinction de casse, ß doit être considéré comme égal à SS si vous suivez les règles de mappage de casse d'Unicode. 

Pour obtenir des résultats corrects, la solution la plus simple consiste à installer le module regex de Python qui suit la norme: 

import re
import regex
# enable new improved engine instead of backwards compatible v0
regex.DEFAULT_VERSION = regex.VERSION1 

print(re.match('ß', 'SS', re.IGNORECASE)) # none
print(regex.match('ß', 'SS', regex.IGNORECASE)) # matches
1
Voo

Une autre solution simple consiste à transmettre un Tuple à startswith() pour tous les cas nécessaires pour correspondre, par exemple. .startswith(('case1', 'case2', ..)).

Par exemple:

>>> 'Hello'.startswith(('He', 'HE'))
True
>>> 'HEllo'.startswith(('He', 'HE'))
True
>>>
0
Aziz Alto