Понятно, что в худшем случае O(N)
есть несколько очень хороших микрооптимизаций.
Наивный метод выполняет сравнение символов и сравнение конца текста для каждого символа.
Использование часового (то есть копия целевого символа в конце текста) уменьшает количество сравнений до 1 на символ.
На уровне немного
#define haszero(v) ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n) ( haszero((x) ^ (~0UL / 255 * (n))) )
знать, имеет ли какой-либо байт в слове ( x
) конкретное значение ( n
).
Подвыражение v - 0x01010101UL
оценивается как старший бит, установленный в любом байте, когда соответствующий байт в v
ноль или больше, чем 0x80
.
Подвыражение ~v & 0x80808080UL
оценивается старшими битами, установленными в байтах, где байт v
не имеет своего старшего установленного бита (таким образом, байт был меньше чем 0x80
).
Посредством AND этих двух подвыражений ( haszero
) результатом является набор старших бит, где байты в v
были равны нулю, поскольку старшие биты, установленные из-за значения, большего, чем 0x80
в первом подвыражении, маскируются вторым (27 апреля, 1987 Алан Майкрофт).
Теперь мы можем XOR значение для test ( x
) со словом, которое было заполнено байтовым значением, в котором мы заинтересованы ( n
). Поскольку XOR значения с самим собой приводит к нулевому байту и ненулевой в противном случае, мы можем передать результат haszero
.
Это часто используется в типичной strchr
реализации.
(Стивен Беннет предложил это 13 декабря 2009 года. Дальнейшие подробности можно найти в хорошо известных « хихикающих битах» ).
PS
этот код не работает для любой комбинации 1111
рядом с0
Хак проходит тест грубой силы (просто наберитесь терпения):
#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;
}
Много голосов за ответ, который делает предположение, что один символ = один байт, что в настоящее время уже не стандарт
Спасибо за замечание.
Ответ должен был быть чем угодно, но не эссе о многобайтовой / переменной ширине кодировки :-) (честно говоря, это не моя область знаний, и я не уверен, что это то, что ищет ОП).
В любом случае, мне кажется, что вышеприведенные идеи / приемы могут быть в некоторой степени адаптированы к MBE (особенно самосинхронизирующиеся кодировки ):
- как отмечено в комментарии Йохана, хак «легко» может быть расширен для работы с двойными байтами или чем-то еще (конечно, вы не можете растянуть его слишком сильно);
- типичная функция, которая находит символ в многобайтовой символьной строке:
- сторожевую технику можно использовать с небольшим предвидением.