Se entiende que el peor de los casos es que O(N)
hay algunas micro optimizaciones muy buenas.
El método ingenuo realiza una comparación de caracteres y una comparación de final de texto para cada carácter.
El uso de un centinela (es decir, una copia del carácter objetivo al final del texto) reduce el número de comparaciones a 1 por carácter.
En el nivel de twiddling hay:
#define haszero(v) ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n) ( haszero((x) ^ (~0UL / 255 * (n))) )
para saber si algún byte en una palabra ( x
) tiene un valor específico ( n
).
La subexpresión se v - 0x01010101UL
evalúa como un conjunto de bits alto en cualquier byte siempre que el byte correspondiente v
sea cero o mayor que 0x80
.
La subexpresión se ~v & 0x80808080UL
evalúa en bits altos establecidos en bytes donde el byte de v
no tiene su conjunto de bits alto (por lo que el byte era menor que 0x80
).
Al ANDing estas dos subexpresiones ( haszero
), el resultado es el conjunto de bits altos donde los bytes en v
eran cero, ya que los bits altos establecidos debido a un valor mayor que 0x80
en la primera subexpresión se enmascaran en la segunda (27 de abril, 1987 por Alan Mycroft).
Ahora podemos XOR el valor para probar ( x
) con una palabra que se ha llenado con el valor de byte en el que estamos interesados ( n
). Debido a que XORing un valor consigo mismo da como resultado un byte cero y distinto de cero, podemos pasar el resultado a haszero
.
Esto se usa a menudo en una strchr
implementación típica .
(Stephen M Bennet sugirió esto el 13 de diciembre de 2009. Más detalles en los conocidos Bit Twiddling Hacks ).
PD
este código está roto para cualquier combinación de 1111
's al lado de un0
El truco pasa la prueba de fuerza bruta (solo sea 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;
}
Muchos votos a favor por una respuesta que hace que la suposición sea un carácter = un byte, que hoy en día ya no es el estándar
Gracias por el comentario
La respuesta debía ser cualquier cosa menos un ensayo sobre codificaciones de varios bytes / ancho variable :-) (para ser justos, esa no es mi área de especialización y no estoy seguro de que sea lo que estaba buscando el OP).
De todos modos, me parece que las ideas / trucos anteriores podrían adaptarse de alguna manera al MBE (especialmente las codificaciones de sincronización automática ):
- como se señaló en el comentario de Johan, el hack puede "fácilmente" extenderse para trabajar por bytes dobles o cualquier otra cosa (por supuesto, no se puede estirar demasiado);
- Una función típica que localiza un carácter en una cadena de caracteres multibyte:
- La técnica centinela se puede utilizar con un poco de previsión.