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
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.
Que dis-tu de ça:
prefix = 'he'
if myVeryLongStr[:len(prefix)].lower() == prefix.lower()
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)
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
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
>>>