Entendendo-se que o pior caso é O(N)
, existem algumas micro-otimizações muito boas.
O método ingênuo realiza uma comparação de caracteres e uma comparação de final de texto para cada caractere.
O uso de uma sentinela (ou seja, uma cópia do caractere alvo no final do texto) reduz o número de comparações para 1 por caractere.
No nível de rodízio de bits, há:
#define haszero(v) ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n) ( haszero((x) ^ (~0UL / 255 * (n))) )
para saber se algum byte em uma palavra ( x
) tem um valor específico ( n
).
A subexpressão é v - 0x01010101UL
avaliada como um conjunto de bits alto em qualquer byte sempre que o byte correspondente v
for zero ou maior que 0x80
.
A subexpressão é ~v & 0x80808080UL
avaliada como bits altos definidos em bytes onde o byte de v
não possui seu conjunto de bits alto (portanto, o byte era menor que 0x80
).
Ao ANDing essas duas subexpressões ( haszero
), o resultado é o conjunto de bits altos em que os bytes v
foram zero, pois os bits altos configurados devido a um valor maior que 0x80
na primeira subexpressão são mascarados no segundo (27 de abril de 1987 por Alan Mycroft).
Agora podemos XOR o valor para testar ( x
) com uma palavra que foi preenchida com o valor de bytes em que estamos interessados ( n
). Como XORing um valor em si resulta em zero byte e diferente de zero, caso contrário, podemos passar o resultado para haszero
.
Isso geralmente é usado em uma strchr
implementação típica .
(Stephen M Bennet sugeriu isso em 13 de dezembro de 2009. Mais detalhes no conhecido Bit Twiddling Hacks ).
PS
esse código está quebrado para qualquer combinação de 1111
s ao lado de um0
O hack passa no teste de força bruta (apenas seja paciente):
#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;
}
Muitos votos positivos para uma resposta que torna a suposição um caractere = um byte, que hoje em dia não é mais o padrão
Obrigado pela observação.
A resposta era para ser apenas um ensaio sobre codificações de vários bytes / largura variável :-) (com toda a justiça, essa não é a minha área de especialização e não tenho certeza de que é o que o OP estava procurando).
De qualquer forma, parece-me que as idéias / truques acima poderiam ser adaptados ao MBE (especialmente codificações auto-sincronizáveis ):
- como observado no comentário de Johan, o hack pode 'facilmente' ser estendido para trabalhar com bytes duplos ou qualquer coisa (é claro que você não pode esticá-lo demais);
- uma função típica que localiza um caractere em uma cadeia de caracteres multibyte:
- contém chamadas para
strchr
/ strstr
(por exemplo, GNUlib coreutils mbschr )
- espera que eles estejam bem sintonizados.
- a técnica sentinela pode ser usada com um pouco de previsão.