Przyjmuje się, że najgorszym przypadkiem jest O(N)
kilka bardzo dobrych mikrooptymalizacji.
Naiwna metoda przeprowadza porównanie znaków i porównanie końca tekstu dla każdego znaku.
Użycie wartownika (tj. Kopii znaku docelowego na końcu tekstu) zmniejsza liczbę porównań do 1 na znak.
Na nieco kręcącym się poziomie jest:
#define haszero(v) ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n) ( haszero((x) ^ (~0UL / 255 * (n))) )
wiedzieć, czy dowolny bajt w słowie ( x
) ma określoną wartość ( n
).
Podwyrażenie v - 0x01010101UL
, ocenia na wysoki bit ustawiony w dowolnym bajcie, ilekroć odpowiedni bajt v
jest równy zero lub większy niż 0x80
.
Podwyrażenie ~v & 0x80808080UL
ocenia na wysokie bity ustawione w bajtach, w których bajt v
nie ma ustawionego wysokiego bitu (więc bajt był mniejszy niż 0x80
).
Dzięki ANDingowi tych dwóch podwyrażeń ( haszero
) wynikiem jest zestaw wysokich bitów, w którym bajty v
były zerowe, ponieważ zestaw wysokich bitów z powodu wartości większej niż 0x80
w pierwszym podwyrażeniu jest maskowany przez drugi (27 kwietnia, 1987 Alan Mycroft).
Teraz możemy XOR wartość testować ( x
) słowem wypełnionym wartością bajtu, która nas interesuje ( n
). Ponieważ XORing samej wartości powoduje zerowy bajt, w przeciwnym razie niezerowy, możemy przekazać wynik do haszero
.
Jest to często używane w typowej strchr
implementacji.
(Stephen M. Bennet zasugerował to 13 grudnia 2009 r. Dalsze szczegóły w dobrze znanym Bit Twiddling Hacks ).
PS
ten kod jest uszkodzony dla dowolnej kombinacji 1111
znaków obok0
Hack przechodzi test brutalnej siły (bądź cierpliwy):
#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;
}
Dużo głosów poparcia dla odpowiedzi, która sprawia, że założenie to jeden znak = jeden bajt, co obecnie nie jest już standardem
Dziękuję za uwagę.
Odpowiedź miała być tylko esejem na temat kodowania wielobajtowego / o zmiennej szerokości :-) (szczerze mówiąc, to nie jest moja specjalizacja i nie jestem pewien, czy tego szukał PO).
W każdym razie wydaje mi się, że powyższe pomysły / sztuczki można nieco dostosować do MBE (szczególnie kodowania samosynchronizującego ):
- jak zauważono w komentarzu Johana, hack można „łatwo” rozszerzyć na podwójne bajty lub cokolwiek innego (oczywiście nie można tego zbytnio rozciągnąć);
- typowa funkcja, która lokalizuje znak w ciągu znaków wielobajtowych:
- technika wartownika może być użyta z odrobiną dalekowzroczności.