Étant entendu que le pire des cas est O(N)
, il y a de très belles micro-optimisations.
La méthode naïve effectue une comparaison de caractères et une comparaison de fin de texte pour chaque caractère.
L'utilisation d'une sentinelle (c'est-à-dire une copie du caractère cible à la fin du texte) réduit le nombre de comparaisons à 1 par caractère.
Au niveau du bit twiddling, il y a:
#define haszero(v) ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n) ( haszero((x) ^ (~0UL / 255 * (n))) )
pour savoir si un octet dans un mot ( x
) a une valeur spécifique ( n
).
La sous v - 0x01010101UL
- expression est évaluée à un bit élevé défini dans n'importe quel octet chaque fois que l'octet correspondant dans v
est nul ou supérieur à 0x80
.
La sous-expression est ~v & 0x80808080UL
évaluée à des bits élevés définis en octets où l'octet de v
n'a pas son bit élevé défini (donc l'octet était inférieur à 0x80
).
En ANDant ces deux sous-expressions ( haszero
), le résultat est l'ensemble des bits élevés où les octets v
étaient nuls, car les bits élevés définis en raison d'une valeur supérieure à celle 0x80
de la première sous-expression sont masqués par la seconde (27 avril, 1987 par Alan Mycroft).
Maintenant, nous pouvons XOR la valeur à tester ( x
) avec un mot qui a été rempli avec la valeur d'octet qui nous intéresse ( n
). Étant donné que XOR une valeur avec elle-même entraîne un octet nul et sinon différent de zéro, nous pouvons transmettre le résultat à haszero
.
Ceci est souvent utilisé dans une strchr
implémentation typique .
(Stephen M Bennet l'a suggéré le 13 décembre 2009. Plus de détails dans les fameux Bit Twiddling Hacks ).
PS
ce code est rompu pour toute combinaison de à 1111
côté d'un0
Le hack passe le test de force brute (soyez patient):
#include <iostream>
#include <limits>
bool haszero(std::uint32_t v)
{
return (v - std::uint32_t(0x01010101)) & ~v & std::uint32_t(0x80808080);
}
bool hasvalue(std::uint32_t x, unsigned char n)
{
return haszero(x ^ (~std::uint32_t(0) / 255 * n));
}
bool hasvalue_slow(std::uint32_t x, unsigned char n)
{
for (unsigned i(0); i < 32; i += 8)
if (((x >> i) & 0xFF) == n)
return true;
return false;
}
int main()
{
const std::uint64_t stop(std::numeric_limits<std::uint32_t>::max());
for (unsigned c(0); c < 256; ++c)
{
std::cout << "Testing " << c << std::endl;
for (std::uint64_t w(0); w != stop; ++w)
{
if (w && w % 100000000 == 0)
std::cout << w * 100 / stop << "%\r" << std::flush;
const bool h(hasvalue(w, c));
const bool hs(hasvalue_slow(w, c));
if (h != hs)
std::cerr << "hasvalue(" << w << ',' << c << ") is " << h << '\n';
}
}
return 0;
}
Beaucoup de votes positifs pour une réponse qui fait l'hypothèse un caractère = un octet, ce qui n'est plus la norme de nos jours
Merci pour la remarque.
La réponse devait être autre chose qu'un essai sur les codages multi-octets / à largeur variable :-) (en toute honnêteté, ce n'est pas mon domaine d'expertise et je ne suis pas sûr que ce soit ce que l'OP recherchait).
Quoi qu'il en soit, il me semble que les idées / astuces ci-dessus pourraient être quelque peu adaptées au MBE (en particulier les encodages auto-synchronisés ):
- comme indiqué dans le commentaire de Johan, le hack peut être «facilement» étendu pour fonctionner sur deux octets ou autre (bien sûr, vous ne pouvez pas trop l'étirer);
- une fonction typique qui localise un caractère dans une chaîne de caractères multi-octets:
- contient des appels à
strchr
/ strstr
(par exemple GNUlib coreutils mbschr )
- s'attend à ce qu'ils soient bien réglés.
- la technique sentinelle peut être utilisée avec un peu de prévoyance.