Es versteht sich, dass es im schlimmsten Fall O(N)
einige sehr schöne Mikrooptimierungen gibt.
Die naive Methode führt für jedes Zeichen einen Zeichenvergleich und einen Textende-Vergleich durch.
Durch die Verwendung eines Sentinels (dh einer Kopie des Zielzeichens am Ende des Texts) wird die Anzahl der Vergleiche auf 1 pro Zeichen reduziert.
Auf der Ebene des Bit Twiddling gibt es:
#define haszero(v) ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n) ( haszero((x) ^ (~0UL / 255 * (n))) )
um zu wissen, ob ein Byte in einem Wort ( x
) einen bestimmten Wert ( n
) hat.
Der Unterausdruck v - 0x01010101UL
wird zu einem hohen Bit ausgewertet, das in einem beliebigen Byte gesetzt ist, wenn das entsprechende Byte in v
Null oder größer als ist 0x80
.
Der Unterausdruck wird ~v & 0x80808080UL
zu hohen Bits ausgewertet, die in Bytes gesetzt sind, bei denen das hohe Bit des Bytes v
nicht gesetzt ist (das Byte war also kleiner als 0x80
).
Durch UND-Verknüpfung dieser beiden Unterausdrücke ( haszero
) wird das High-Bit-Set erhalten, bei dem die Bytes v
Null waren, da die High-Bits, die aufgrund eines höheren Wertes als 0x80
im ersten Unterausdruck gesetzt wurden, vom zweiten maskiert werden (27. April). 1987 von Alan Mycroft).
Jetzt können wir den zu testenden Wert ( x
) mit einem Wort XOR-verknüpfen, das mit dem Byte-Wert gefüllt ist, an dem wir interessiert sind ( n
). Da das XOR-Verknüpfen eines Werts mit sich selbst zu einem Null-Byte und zu einem Wert ungleich Null führt, können wir das Ergebnis an übergeben haszero
.
Dies wird häufig in einer typischen strchr
Implementierung verwendet.
(Stephen M Bennet schlug dies am 13. Dezember 2009 vor. Weitere Details in den bekannten Bit Twiddling Hacks ).
PS
Dieser Code ist für jede Kombination von 1111
's neben a ungültig0
Der Hack besteht den Brute-Force-Test (nur etwas Geduld):
#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;
}
Viele positive Stimmen für eine Antwort, die von einem Zeichen = einem Byte ausgeht, was heutzutage nicht mehr der Standard ist
Danke für die Bemerkung.
Die Antwort sollte alles andere als ein Aufsatz über Multi-Byte- / Variable-Width-Codierungen sein :-) (Fairerweise ist das nicht mein Fachgebiet und ich bin nicht sicher, ob es das ist, wonach das OP gesucht hat).
Jedenfalls scheint es mir, dass die obigen Ideen / Tricks etwas an MBE angepasst werden könnten (insbesondere selbstsynchronisierende Codierungen ):
- wie in Johans Kommentar vermerkt der Hack 'leicht' erweitert werden, um für Doppelbytes oder irgendetwas zu arbeiten (natürlich kann man ihn nicht zu sehr dehnen).
- Eine typische Funktion, die ein Zeichen in einer Multibyte-Zeichenfolge findet:
- Die Sentinel-Technik kann mit ein wenig Weitsicht angewendet werden.