已经给出了一些答案。反正。我想向您展示背景和一些替代解决方案。
让我们首先检查您的尝试。你在写:
if (text.at(i) == 'a' || 'e' || 'i' || 'o' || 'u' || 'y')
并希望索引中的字母 I 将与所有提到的字母进行比较。但这不会发生。为什么?
if-statement 需要一个布尔表达式。将评估此表达式。如果结果等于 true,则执行if 之后的语句或复合语句。 OK,那我们看一下必须求值的表达式:
text.at(i) == 'a' || 'e' || 'i' || 'o' || 'u' || 'y'
由操作数和运算符组成。运算符具有属性。其中之一是优先级。一些运算符的优先级高于其他运算符。这将导致“2+3*4” = 14 而不是 =10。 “*”的优先级高于“+”。因此,首先进行乘法运算,然后进行加法运算。你也可以写“2+ (3*4)”。完全一样。
下一个属性是关联性。如果优先级相等,则关联性将决定以什么顺序评估表达式。关联性是“左”或“右”。这意味着,操作数是从左到右或从右到左计算的。
在 CPP 参考资料here 中有一个非常好的描述。请阅读这个。如果我们对此进行分析,我们将看到比较运算符== 的优先级高于逻辑or。所以,我们已经可以将表达式改写为:
(text.at(i) == 'a') || 'e' || 'i' || 'o' || 'u' || 'y'
以下逻辑or 运算符具有相同的优先级。然后,关联性将决定评估的顺序:从左到右。所以,我们现在可以将表达式重写为
((text.at(i) == 'a') || 'e') || 'i' || 'o' || 'u' || 'y' // then
(((text.at(i) == 'a') || 'e') || 'i' ) || 'o' || 'u' || 'y' // then
((((text.at(i) == 'a') || 'e') || 'i' ) || 'o' ) || 'u' || 'y' // then finally
(((((text.at(i) == 'a') || 'e') || 'i' ) || 'o' ) || 'u' ) || 'y'
好的,接下来我们需要了解字符文字,例如“a”或“e”具有值。它们通常用 ASCII 编码。然后“a”的值为 97,“e”的值为 101,依此类推。您可以查看 ASCII 表。但是如何将这些值解释为布尔值true 或false?很简单:C++ 将 0 视为 false,将所有其他值视为 true。
此外,您需要了解布尔短路评估。请阅读here
这意味着:如果结果已经明确,则不会执行进一步的操作。例如。如果( text.at(i) == 'a'),那么,如果字母真的是'a',那么结果是真的。将不再进行任何操作。简单的。如果不是,则表达式将导致:
(((((false) || 'e') || 'i' ) || 'o' ) || 'u' ) || 'y'
然后,评估将是false || ‘e’。并且“e”等于 101,结果为:false||101。这总是正确的。评价结束。不会再做任何事情了。结果要么是真的,因为字母是“a”,要么是无论如何。这被称为重言式:总是正确的。因此,所有字符都被打印出来了。
而且,我们的朋友,启用所有警告或静态代码分析的编译器,会发现这个问题并通知我们。
我在另一个答案中看到了类似std::cout << 97 || 101 || 105 || 111 || 117 || 121; 的示例,我的猜测是作者并不完全理解它的含义。这里插入运算符<< 的优先级高于逻辑或运算符||`。您也可以将其重写为:
(((((std::cout << 97) || 101 ) || 105 ) || 111 ) || 117 ) || 121;
如您所见。首先,我们将数字 97 写入std::cout 插入器运算符将始终返回对调用它的流的引用。所以,接下来我们有:
((((std::cout || 101 ) || 105 ) || 111 ) || 117 ) || 121;
现在,std::cout 用于布尔表达式。你猜怎么着? std::cout有一个布尔运算符(参见here)。如果一切正常,这将返回true。并且由于可以向std::cout 写入内容,因此将启动短路评估,并且不会执行进一步的操作。结果语句与在一行中写入“true;”相同。输出只是布尔表达式求值的副作用。但这只是一个旁注。
我们从中学到了什么。我们应该真正理解我们正在编写的表达式。而且,一个非常重要的建议:使用括号,即使是多余的。始终使用许多括号来明确您的意思。如果您使用括号,您会自己识别错误。
是不是很惊讶,要分析这么简单的一句话,需要多少解释?但现在我们了解并可以着手解决。
推荐解决方案
为了将一个字母与所有元音'a'、'e'、'I'、'o'、'u'等进行比较,我们需要将字母与所有元音'a进行比较'、'e'、'I'、'o'、'u' 等等。是的。就是这样:
if ( (text.at(i) == 'a') || (text.at(i) == 'e') || (text.at(i) == 'i') || (text.at(i) == 'o') || (text.at(i) == 'u') ) . . .
顺便说一句,字母“y”不被视为元音。反正。你可以搜索你想要的。
此外,还有许多不同的解决方案。
有些比其他更快,有些或更直观,有些需要更少的打字工作等等。
其他答案中显示的 2 个解决方案,使用 std::set 和 find 或 std::array 和 std::find 不是最快的解决方案。
在std::set 的情况下,std::unordered_set 连同它的count 函数会更有效和更快,因为std::unordered_set 使用快速算法进行搜索。而std::find也可以通过简单的计算来跑赢。这个我会在最后展示。
如果您更喜欢更易读的东西(并且可能了解其他语言),您可以模拟in-like 操作,例如:
if (5, in{ 1, 2, 3, 4, 5 }) . . .
但是为此,您需要做一些非常先进的事情。我只是想在这里向你展示什么是可能的。正如我所说,有许多不同的可能解决方案。对于上述语句,我们需要为 std::initializer_list 额外定义一些包装类并重载其逗号运算符。正如我所说的,一些沉重的东西:
template<class T>
struct in {
in(const std::initializer_list<T>& il) : ref(il) {}
const std::initializer_list<T>& ref;
};
template<class T>
bool operator,(const T& lhs, const is_in<T>& rhs) {
return std::find(rhs.ref.begin(), rhs.ref.end(), lhs) != rhs.ref.end();
}
暂时忘记它。
现在,最后但并非最不重要的是,alpha ASCII 字母的最快解决方案。 if-语句:
if ( 0x02208222 >> (text.at[i] & 0x1f)) & 1) . . .
会给你你需要的。我们这里只有一个快速的按位,然后是按位移位,然后是按位与。这可以被评估为超快。
但是如何破解呢?
这种技术非常古老,在古代使用,当时人们在他们的 8 位微控制器上使用汇编程序。我们依赖 ASCII 来表示字母和发明者,他们在定义代码时做出了一些明智的决定。
请看下图。
如果您查看大写字母和小写字母,您会发现只有前 5 位对于编码任何 apha 字母很重要。这很清楚,因为使用 5 位,您可以对 0..31 之间的值进行编码。而我们在西方语言中只有 26 个字母。位号 5(第六位,从 0 开始计数)决定情况。设置的位数 5 表示小写。您可以在表格中清楚地看到这一点。正因为如此,小写字母和大写字母之间的差值始终为 32。在二进制中,设置了位数为 5(第六位,计数从 0 开始)的数字。而且,如果我们想忽略这种情况,那么我们使用0001 1111b 进行位与(掩码),即0x1fhex,我们得到一个从 1 到 26 的数字等价于字母 'A' - 'Z ' 对于任何情况。那是text.at[i] & 0x1f的部分。啊哈。
下一个聪明的想法是对我们想要在位位置搜索的字母进行编码。例如。我们取一个 32 位无符号整数值。在这个值中,我们根据字母的数字来设置位。例如,如果我们搜索“a”,那么我们会看到“a”有一个关联的数字 1,我们将位编号设置为 1(第 2 位,从 0 开始计数)。如果我们另外寻找一个“e”,我们在我们的值中设置位号 5(第六位,从 0 开始计数)。对于我们想要搜索的任何一个字母字符,我们在我们的 32 位 unsigned int 值中设置相应的位。
对您要搜索的所有字母进行此操作后,我们会得到结果数字 0x02208222 hex。啊哈。现在我们知道了,这是从这里来的。如果我们现在想检查某个字母,那么我们将这个数字中的位向右移动很多位置,正如其对应的字母等效所指示的那样。如果我们在可能的最低位置找到一个设置位,那么我们就有一个肯定的匹配。
复杂,但很酷。超快速地找到所有可能的字母集。
但我承认,你需要考虑一下。 . .
所以,我希望你能给你一个可以理解的解释。如果有问题,请提出。
.
基准测试
为了比较不同的解决方案,我添加了一个基准。结果是显着的。我用 10 亿个随机测试用例运行了 3 次测试。
结果排名:
1. ASCII approach 100%
2. Unordered_set 395%
3. Standard comparison 522%
4. constexpr array 1046%
5. set 1638%
基准的实施:
#include <array>
#include <iostream>
#include <random>
#include <algorithm>
#include <chrono>
#include <unordered_set>
#include <set>
constexpr size_t TestDataSize = 1'000'000'000u;
constexpr size_t NumberOfTestRuns = 3u;
using TestData = std::array<char, TestDataSize>;
const char SourceCharacter[]{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"};
TestData testData;
bool isVowel_ASCII(const char c) {
return (0x02208222 >> (c & 0x1f)) & 1;
}
static constexpr std::array<char, 12> vowels{ 'A', 'E', 'I', 'O', 'U', 'Y', 'a', 'e', 'i', 'o', 'u', 'y' };
bool isVowel_ConstexprArray(const char c) {
return (std::end(vowels) != std::find(begin(vowels),
end(vowels), c));
}
const std::unordered_set<char> vowel_UnorderedSet{ 'A', 'E', 'I', 'O', 'U', 'Y', 'a', 'e', 'i', 'o', 'u', 'y' };
bool isVowel_UnorderedSet(const char c) {
return vowel_UnorderedSet.count(c);
}
const std::set<char> vowel_Set{ 'A', 'E', 'I', 'O', 'U', 'Y', 'a', 'e', 'i', 'o', 'u', 'y' };
bool isVowel_Set(const char c) {
return (vowel_Set.find(c) != vowel_Set.end());;
}
bool isVowel_Standard(const char c) {
return (c == 'a') || (c == 'e') || (c == 'i') || (c == 'o') || (c == 'u') || (c == 'y') ||
(c == 'A') || (c == 'E') || (c == 'I') || (c == 'O') || (c == 'U') || (c == 'Y') ;
}
int main() {
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_int_distribution<> intRange(0, sizeof(SourceCharacter)-1);
size_t counter_ASCII{};
size_t counter_ConstexprArray{};
size_t counter_UnorderedSet{};
size_t counter_Set{};
size_t counter_Standard{};
auto start = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start);
for (size_t testRun{}; testRun < NumberOfTestRuns; ++testRun) {
std::cout << "\n\n\nTest run " << (testRun + 1) << "\n\n";
std::generate(testData.begin(), testData.end(), [&]() {return SourceCharacter[intRange(generator)]; });
counter_ASCII = 0u;
counter_ConstexprArray = 0u;
counter_UnorderedSet = 0u;
counter_Set = 0u;
counter_Standard = 0;
// --------------------------------------------------------------------------------
start = std::chrono::system_clock::now();
for (size_t testCase{}; testCase < TestDataSize; ++testCase) {
if (isVowel_ASCII(testData[testCase])) ++counter_ASCII;
}
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start);
std::cout << "Test 'ASCII` solution. Resulting vowel count: " << counter_ASCII << "\t Duration: " << elapsed.count() << " ms\n";
// --------------------------------------------------------------------------------
start = std::chrono::system_clock::now();
for (size_t testCase{}; testCase < TestDataSize; ++testCase) {
if (isVowel_UnorderedSet(testData[testCase])) ++counter_UnorderedSet;
}
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start);
std::cout << "Test 'unordered_set' solution. Resulting vowel count: " << counter_UnorderedSet << "\t Duration: " << elapsed.count() << " ms\n";
// --------------------------------------------------------------------------------
start = std::chrono::system_clock::now();
for (size_t testCase{}; testCase < TestDataSize; ++testCase) {
if (isVowel_Standard(testData[testCase])) ++counter_Standard;
}
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start);
std::cout << "Test 'standard' solution. Resulting vowel count: " << counter_Standard << "\t Duration: " << elapsed.count() << " ms\n";
// --------------------------------------------------------------------------------
start = std::chrono::system_clock::now();
for (size_t testCase{}; testCase < TestDataSize; ++testCase) {
if (isVowel_ConstexprArray(testData[testCase])) ++counter_ConstexprArray;
}
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start);
std::cout << "Test 'Constexpr array` solution. Resulting vowel count: " << counter_ConstexprArray << "\t Duration: " << elapsed.count() << " ms\n";
// --------------------------------------------------------------------------------
start = std::chrono::system_clock::now();
for (size_t testCase{}; testCase < TestDataSize; ++testCase) {
if (isVowel_Set(testData[testCase])) ++counter_Set;
}
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - start);
std::cout << "Test 'set' solution. Resulting vowel count: " << counter_Set << "\t Duration: " << elapsed.count() << " ms\n";
}
}
(编译时优化)
结果:
Test run 1
Test 'ASCII` solution. Resulting vowel count: 226384481 Duration: 920 ms
Test 'unordered_set' solution. Resulting vowel count: 226384481 Duration: 3630 ms
Test 'standard' solution. Resulting vowel count: 226384481 Duration: 4780 ms
Test 'Constexpr array` solution. Resulting vowel count: 226384481 Duration: 9620 ms
Test 'set' solution. Resulting vowel count: 226384481 Duration: 15060 ms
Test run 2
Test 'ASCII` solution. Resulting vowel count: 226421607 Duration: 920 ms
Test 'unordered_set' solution. Resulting vowel count: 226421607 Duration: 3630 ms
Test 'standard' solution. Resulting vowel count: 226421607 Duration: 4800 ms
Test 'Constexpr array` solution. Resulting vowel count: 226421607 Duration: 9650 ms
Test 'set' solution. Resulting vowel count: 226421607 Duration: 15060 ms
Test run 3
Test 'ASCII` solution. Resulting vowel count: 226416438 Duration: 920 ms
Test 'unordered_set' solution. Resulting vowel count: 226416438 Duration: 3630 ms
Test 'standard' solution. Resulting vowel count: 226416438 Duration: 4800 ms
Test 'Constexpr array` solution. Resulting vowel count: 226416438 Duration: 9620 ms
Test 'set' solution. Resulting vowel count: 226416438 Duration: 15070 ms
正如预期的那样。
- ASCII 解决方案是最快的。它就是为此目的而设计的。但它仅适用于 alpha ASCII 字母,不那么兼容,不容易理解,也不那么可读。 . .
-
std::unordered_set 速度极快,易于阅读和理解,因此是惯用的正确方法
- 比较所有元音的幼稚方法令人惊讶。永远不要低估一个优化的编译器。 . .
@OP 请使用推荐的解决方案