Reputation: 374
Given any character, what's the fastest way for me to determine if that character belongs to a set (not the container-type) of known characters.
In other words, what's the fastest elegant way to implement the conditional:
char c = 'a';
if(c == ch1 || c == ch2 || c == ch3 ...) // Do something...
Is there an STL container (I'm thinking it might be unordered_set?) that I can just pass the character as a key to and it'll return true if the key exists?
Anything with an O(1) lookup time will work for me.
Upvotes: 11
Views: 5680
Reputation: 5310
@dtech's answer is fabulous, I only give some special cases for digital or letter judgement.
#define IS_DIGIT(x) \
(static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
#define IS_LOWER_LETTER(x) \
(static_cast<unsigned int>((x) - 'a') < static_cast<unsigned int>(26))
#define IS_UPPER_LETTER(x) \
(static_cast<unsigned int>((x) - 'A') < static_cast<unsigned int>(26))
#define IS_LETTER(x) \
(IS_LOWER_LETTER(x) || IS_UPPER_LETTER(x))
Upvotes: 0
Reputation: 460
Such a function is available in C and C++. I guess it is also a relative fast function. I've included a C++ example application that must detect whether a character is a separator.
#include <cstring>
bool is_separator(char c)
{
return std::strchr(" \f\t\v\r\n\\+-=()", c); // this includes \0!
}
Upvotes: 1
Reputation: 73590
Typically this kind of test is not isolated, i.e. you don't just have
if(c==ch1 || c==ch2 || c=ch3 ) { ... }
But
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);
}
Optimising each of the if
statements is possibly less efficient than considering all these parts at once. What this kind of structure usually means is that groups of characters are being considered equivalent in some ways - and that is what we might want to express in the code.
In this case, I'd build up a character traits array that contains the character type information.
// 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;
}
Now your conditions become
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);
}
I'd usually extract the char_traits
variable into its own include, and generate that include using a simple program too. This keeps things easy to change going forwards.
Upvotes: 2
Reputation: 20025
You could create an array of booleans and assign the value true
for each character in wanted set. For example if your wanted set consists of 'a', 'd', 'e'
:
bool array[256] = {false};
array['a'] = true;
array['d'] = true;
array['e'] = true;
and then you can check a character c
:
if (array[c]) ...
We could also use a bitset for this purpose:
std::bitset<256> b;
b.set('a');
b.set('d');
b.set('e');
and checking as:
if (b.test(c)) ...
Upvotes: 10
Reputation: 49319
I went a little further and wrote two versions, one based on a lookup array, the other on a set using an underlying hash.
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;
};
Then wrote a little benchmark, added a few more containers for the sake of comparison. Lower is better, the data points are for "character set size / text size":
Seems like for short char sets and text, std::string::find_first_of
is fastest, even faster than using a lookup array, but quickly dwindles as the test size increases. std::vector<bool>
seems like the "golden mean", QBitArray
probably has a little different implementation because it pulls ahead as the test size increases, at the largest test QVector<bool>
is fastest, presumably because it doesn't have the overhead of bit access. The two hash sets are close, trading places, last and least there is the std::set
.
Tested on an i7-3770k Win7 x64 box, using MinGW 4.9.1 x32 with -O3.
Upvotes: 16
Reputation: 21803
Keep it simple.
static const char my_chars[] = { 'a', 'b', 'c' };
if (std::find(std::begin(my_chars), std::end(my_chars), char_to_test))
The lookup is linear, but that doesn't tell the whole story. Other data structures may give constant lookup, but the constant can be higher than the maximum "linear" value! If search times with increasing n are O(1) = (100, 100, 100) and O(n) = (10, 20, 30), then you can see that O(n) is faster than O(1) for these small n.
As there are only a small number of characters, I would be very surprised if the simple linear search measures slower than the alternatives in some real code.
If you ensure the array is sorted, you can also try std::binary_search
. I don't know if it will be faster or slower for a small number of values.
As always, measure to be sure.
Upvotes: 5
Reputation: 4748
Have you tried checking your single character against a string of the characters you want to compare against?
Upvotes: 0