确定字符是否属于一组已知字符的最快方法 C++

Fastest Way to Determine if Character Belongs to a Set of Known Characters C++

给定任何字符,确定该字符是否属于已知字符集(而非容器类型)的最快方法是什么。

换句话说,实现条件的最快速优雅的方法是什么:
char c = 'a';
if(c == ch1 || c == ch2 || c == ch3 ...) // Do something...

是否有一个 STL 容器(我想它可能是 unordered_set?),我可以将字符作为键传递给它,如果键存在,它将 return 为真?

任何具有 O(1) 查找时间的东西都适合我。

您是否尝试过将您的单个字符与要比较的字符的字符串进行比较?

std::string::find_first_of()

您可以创建一个布尔值数组,并为所需集中的每个字符分配值 true。例如,如果您想要的集合包含 'a', 'd', 'e'

bool array[256] = {false};
array['a'] = true;
array['d'] = true;
array['e'] = true;

然后你可以检查一个字符c:

if (array[c]) ... 

我们也可以为此目的使用位集:

std::bitset<256> b;
b.set('a');
b.set('d');
b.set('e');

并检查为:

if (b.test(c)) ...

通常这种测试不是孤立的,即你不只是有

if(c==ch1 || c==ch2 || c=ch3 ) { ... }

但是

if(c==ch1 || c==ch2 || c=ch3 ) {
    handle_type_a(c);
}
else if(c==ch4 || c==ch5 || c=ch6 ) {
    handle_type_b(c);
}    
else if(c==ch7 || c==ch8 || c=ch9 ) {
    handle_type_c(c);
}

if(c==ch4 || c==ch6 || c=ch7 ) {
    handle_magic(c);
}

优化每个 if 语句的效率可能低于同时考虑所有这些部分。这种结构通常意味着字符组在某些方面被认为是等价的——这就是我们可能想要在代码中表达的意思。

在这种情况下,我将构建一个包含字符类型信息的字符特征数组。

// First 2 bits contains the "type" of the character
static const unsigned char CHAR_TYPE_BITS = 3;
static const unsigned char CHAR_TYPE_A = 0;  
static const unsigned char CHAR_TYPE_B = 1;
static const unsigned char CHAR_TYPE_C = 2;
// Bit 3 contains whether the character is magic
static const unsigned char CHAR_IS_MAGIC = 4;

static const unsigned char[256] char_traits = {
  ...,
  CHAR_TYPE_A, CHAR_TYPE_B | CHAR_IS_MAGIC ...
  ...
}

static inline unsigned char get_character_type(char c) {
  return char_traits[(unsigned char)c] & CHAR_TYPE_BITS;
}

static inline boolean is_character_magic(char c) {
 return (char_traits[(unsigned char)c] & CHAR_IS_MAGIC) == CHAR_IS_MAGIC;
}

现在你的条件变成了

switch(get_character_type(c)) { 
 case CHAR_TYPE_A:
    handle_type_a(c);
    break;
 case CHAR_TYPE_B:
    handle_type_b(c);
    break;
 case CHAR_TYPE_C:
    handle_type_c(c);
    break;
}

if(is_character_magic(c)) {
  handle_magic(c);
}

我通常会将 char_traits 变量提取到它自己的包含中,并使用一个简单的程序生成包含。这让事情很容易改变。

我走得更远,写了两个版本,一个基于查找数组,另一个基于使用底层哈希的集合。

class CharLookup {
public:
  CharLookup(const std::string & set) : lookup(*std::max_element(set.begin(), set.end()) + 1) {
    for ( auto c : set) lookup[c] = true;
  }
  inline bool has(const unsigned char c) const {
    return c > lookup.size() ? false : lookup[c];
  }
private:
  std::vector<bool> lookup;
};

class CharSet {
public:
  CharSet(const std::string & cset) {
    for ( auto c : cset) set.insert(c);
  }
  inline bool has(const unsigned char c) const {
    return set.contains(c);
  }
private:
  QSet<unsigned char> set;
};

然后写了一点benchmark,加了几个容器方便对比。越低越好,数据点为 "character set size / text size":

似乎对于短字符集和文本,std::string::find_first_of 是最快的,甚至比使用查找数组更快,但随着测试大小的增加而迅速减少。 std::vector<bool> 看起来像 "golden mean",QBitArray 可能有一点不同的实现,因为它随着测试规模的增加而领先,最大测试 QVector<bool> 是最快的,大概是因为它没有位访问的开销。这两个散列集很接近,交易场所,最后也是最不重要的是 std::set.

在 i7-3770k Win7 x64 机器上测试,使用带有 -O3 的 MinGW 4.9.1 x32。

保持简单。

static const char my_chars[] = { 'a', 'b', 'c' };
if (std::find(std::begin(my_chars), std::end(my_chars), char_to_test))

查找是线性的,但这并不能说明全部情况。其他数据结构可能会给出常量查找,但常量可以高于最大"linear"值!如果 n 增加的搜索时间为 O(1) = (100, 100, 100) 和 O(n) = (10, 20, 30),那么您可以看到 O(n) 比 O(1) 快这些小n.

由于只有少量字符,如果简单线性搜索的测量速度比某些 真实 代码中的替代方法慢,我会感到非常惊讶。

如果你保证数组是有序的,你也可以试试std::binary_search。我不知道对于少量的值会更快还是更慢。

一如既往,量力而行。

这样的函数在 C 和 C++ 中可用。我想这也是一个相对快速的功能。我包含了一个 C++ 示例应用程序,它必须检测字符是否为分隔符。

#include <cstring>

bool is_separator(char c)
{
    return std::strchr(" \f\t\v\r\n\+-=()", c); // this includes [=10=]!
}