std::stack
和一些C ++ 11),但仍然很难避免重复。它读取文本文件中的字符按字符,并确定所有左括号和右括号(
{}
,()
和[]
)是否正确匹配。其他所有字符都将被忽略。如果发现不匹配,将显示一条错误消息以指定特定字符,程序将终止。否则,最后会显示一条消息,表明它们都匹配。
以下是可能的错误类型:
丢失打开支架:
int main() { /*...*/ } ] // missing [
缺少关闭支架:
int main() { /*...*/ // missing }
打开支架用错误的闭合支架闭合:
int main() { /*...*/ ] // should close with }
我的问题:
将每个打开的支架推入堆栈这样做的实际方法?我觉得我的方法不太实用,因为它涉及很多条件。找到闭合括号后,必须有某种方法来确定它们是否正确匹配。
std::map
可以用作查找表来将打开和闭合支架相互关联吗?我觉得在这里进行DRY可能会有所帮助。尽管将所有错误检查维护在一个函数中似乎很容易,但是否仍应将它们拆分为单独的函数?如果是这样,应该在消息中还是在消息中显示消息?
如果程序由于匹配错误而终止但没有文件错误,则返回
main()
是否有意义?我不确定是否应该返回EXIT_SUCCESS
,即使如果无法打开文件也已经返回EXIT_FAILURE
。如果这个程序不太实用,我不介意采用完全不同的程序。如果您有更复杂的想法,请分享。我想以正确的方式进行处理。
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <stack>
#include <string>
typedef std::stack<char> Brackets;
void pushOpeningBrackets(Brackets& opening, char ch)
{
if (ch == '{')
opening.push('{');
else if (ch == '(')
opening.push('(');
else if (ch == '[')
opening.push('[');
}
bool errorsFound(Brackets& stack, char openingBracket, char closingBracket)
{
// unmatched?
if (stack.empty())
{
std::cerr << "Unmatched " << closingBracket;
return true;
}
char topBracket = stack.top();
stack.pop();
// not a match?
if (topBracket != openingBracket)
{
if (topBracket == '{')
std::cerr << "Expected } but found " << closingBracket;
else if (topBracket == '(')
std::cerr << "Expected ) but found " << closingBracket;
else if (topBracket == '[')
std::cerr << "Expected ] but found " << closingBracket;
return true;
}
return false;
}
int main()
{
std::cout << "Enter a text file name: ";
std::string filename;
std::getline(std::cin, filename);
std::ifstream inFile(filename.c_str(), std::ios::in);
if (!inFile) return EXIT_FAILURE;
Brackets stack;
std::string fileLine;
while (inFile >> fileLine)
{
for (char ch : fileLine)
{
pushOpeningBrackets(stack, ch);
if (ch == '}')
{
if (errorsFound(stack, '{', '}'))
{
return EXIT_SUCCESS;
}
}
else if (ch == ')')
{
if (errorsFound(stack, '(', ')'))
{
return EXIT_SUCCESS;
}
}
else if (ch == ']')
{
if (errorsFound(stack, '[', ']'))
{
return EXIT_SUCCESS;
}
}
}
}
// checks for missing bracket or full match
if (!stack.empty())
{
char topBracket = stack.top();
stack.pop();
if ('{' == topBracket)
std::cerr << "Missing }";
else if ('(' == topBracket)
std::cerr << "Missing )";
else if ('[' == topBracket)
std::cerr << "Missing ]";
}
else
std::cout << "All brackets match!";
}
#1 楼
代替...void pushOpeningBrackets(Brackets& opening, char ch)
{
if (ch == '{')
opening.push('{');
else if (ch == '(')
opening.push('(');
else if (ch == '[')
opening.push('[');
}
...使用...
void pushOpeningBrackets(Brackets& opening, char ch)
{
switch (ch)
{
case '{':
case '(':
case '[':
opening.push(ch);
break;
}
}
或使用
std:map
:std::map<char,char> charmap;
charmap['('] = ')';
...etc...
void pushOpeningBrackets(Brackets& opening, char ch)
{
if (charmap.find(ch) != charmap.end())
opening.push(ch);
}
同样,代替...
if (topBracket != openingBracket)
{
if (topBracket == '{')
std::cerr << "Expected } but found " << closingBracket;
else if (topBracket == '(')
std::cerr << "Expected ) but found " << closingBracket;
else if (topBracket == '[')
std::cerr << "Expected ] but found " << closingBracket;
return true;
}
if (topBracket != openingBracket)
{
char expected;
switch (topBracket)
{
case '{': expected = '}'; break;
case '(': expected = ')'; break;
case '[': expected = ']'; break;
default: ; // WHAT TO DO HERE?!
}
std::cerr << "Expected " << expected << " but found " << closingBracket;
return true;
}
...或...
if (topBracket != openingBracket)
{
std::cerr << "Expected " << charmap[topBracket] << " but found " << closingBracket;
return true;
}
在现有代码中,在三个不同的位置定义对:例如在这里...
if (errorsFound(stack, '{', '}'))
...在这里...
if ('{' == topBracket)
std::cerr << "Missing }";
...和在这里...
if (topBracket == '{')
std::cerr << "Expected } but found " << closingBracket;
您可以通过以下方式解决此问题:编写子例程
char getCloseOf(char) { ... }
和/或使用std::map
或类似容器在数据中定义它。您尚未在源代码中实现转义字符串和注释。此外,正在解析的文本中的C ++宏可能会使它混乱,例如
#define FOO a(
虽然在一个功能中维护所有错误检查似乎很容易,但是否仍应将它们拆分为单独的功能?如果是这样,应该在消息中还是在main()中显示消息?
我不太喜欢您的子例程。例如,
pushOpeningBrackets
仅被调用一次,并且其实现很短(通过阅读其实现比通过阅读其名称更容易理解它)。因此,您最好将该代码内联而不是作为子例程使用,可能带有注释// push opening brackets
。如果程序从a终止,返回EXIT_SUCCESS是否有意义?匹配错误但不是文件错误?我不确定是否应该返回EXIT_FAILURE,即使它无法打开文件也是如此。
您可以记录几个返回代码,例如:
0:源语法分析-可以
1:源语法分析-BADLY
-2:无法语法分析源-内存错误
#2 楼
我将通过三项主要更改对其进行重写:保持最小的
main()
并改善用户界面。根据“单一责任原则”,将
main()
限制为仅使用适当的参数调用主函数是一个好主意。在这种情况下,功能可以非常清晰地拆分。作为Unix / Linux用户,我希望看到遵循某些Unixy约定的工具:
从命令行上命名的文件中获取输入,如果没有命令行参数,则从标准输入获取输入。
成功后,保持沉默并返回0(除非您添加了对
--verbose
标志的支持,而我没有这样做)麻烦去实现)。在此程序的上下文中,我将认为无法打开输入文件是要报告给
std::cerr
的错误条件,而定界符不匹配将是正常输出。报告给std::cout
。 (要回答您的问题3,我只是走了一条简单的路线,并在遇到错误时将其打印出来。这可能不是最优雅的方法,但是它免除了将不匹配编码为某种表示形式的麻烦。当回答有关程序退出状态的问题4:0表示成功; 2表示程序退出状态。除此之外,还没有通用的约定。
使用
std::string::find_first_of()
。这样可以避免一个又一个字符的繁琐乏味。
将预期的结束定界符保留在堆栈中。
这似乎减少了代码中的冗余。这解决了您的问题1和2。此外,我还对其进行了增强以跟踪行号和列号,以帮助查找不匹配的位置。如果您跟踪每个推入堆栈的定界符的位置,则诊断可能会提供更多信息。
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <stack>
#include <string>
bool bracketsMatch(std::istream &in) {
std::stack<char> expectedDelimiters;
int lineNum = 0;
std::string line;
while (std::getline(in, line)) {
lineNum++;
size_t pos = 0;
while (std::string::npos != (pos = line.find_first_of("(){}[]", pos))) {
int colNum = pos + 1;
switch (line[pos]) {
case '(': expectedDelimiters.push(')'); break;
case '{': expectedDelimiters.push('}'); break;
case '[': expectedDelimiters.push(']'); break;
case ']':
case '}':
case ')':
if (expectedDelimiters.empty()) {
std::cout << "Mismatched " << line[pos]
<< " at line " << lineNum << ", col " << colNum
<< std::endl;
return false;
}
if (line[pos] != expectedDelimiters.top()) {
std::cout << "Expected " << expectedDelimiters.top()
<< ", found " << line[pos]
<< " at line " << lineNum << ", col " << colNum
<< std::endl;
return false;
}
expectedDelimiters.pop();
}
pos = colNum;
}
}
// Should check for a possible input error here, but I didn't bother.
if (!expectedDelimiters.empty()) {
std::cout << "Expected " << expectedDelimiters.top()
<< " at end of file" << std::endl;
return false;
}
return true;
}
int main(int argc, const char *argv[]) {
// The command-line parsing below is a bit sloppy (no validation,
// help message, or option handling, for example).
std::ifstream f;
std::istream &in = (argc > 1) ? (f.open(argv[1]), f) : std::cin;
if (!in) {
std::cerr << argv[0] << ": " << argv[1] << ": "
<< std::strerror(errno) << std::endl;
return 2; // A rather arbitrary error code
}
return bracketsMatch(in) ? EXIT_SUCCESS : EXIT_FAILURE;
}
#3 楼
您正在寻找的技术称为“数据驱动”编程:static char map[256] = {0}; // init all the data to 0.
map['{'] = 1;
map['['] = 1;
map['('] = 1;
map['}'] = '{';
map[']'] = '[';
map[')'] = '(';
for(char ch: data) {
char action = map[ch];
if (action == 0) {
/* do nothing */
}
else if (action == 1) {
stack.push(ch);
}
else {
if (stack.empty()) {
// Error
} else {
char open = stack.top();
stack.pop();
if (open != action) {
// Error
}
}
}
}
if (!stack.empty()) {
// Error
}
将每个开口支架推入堆栈中是否是一种实用的方法?
是的
我觉得我的方法不太实用,因为它涉及很多条件。找到右方括号后,必须有某种方法来确定它们是否正确匹配。
是的,您正在做太多的编程,如果您的需求发生变化,这将涉及更改代码一点点。通过使用数据驱动的方法,您的代码将变得更加灵活,并且只需对用于驱动代码的数据结构进行少量修改,就可以使代码正常运行。
std::map
会是有益的用作将开括号和闭括号彼此关联的查找表?我觉得在这里进行DRY可能会有所帮助。Overkill。
但是需要一个数据结构。
我使用了一个字符数组来保存我的决策状态。如果使用的是unicode或其他较大的字符类型,则使用向量。决策集的大小很小,因此线性扫描也不会花费太多。对于较小的
n
,\ $ O(ln(n))+ K1 \ $可能大于\ $ O(n)+ K2 \ $。您的n
为6。 我喜欢单一日志功能的概念。您可以在单个位置保持一致的格式化消息的方式。
如果是这样,应该在消息中还是在消息中显示消息?
main()
中?您可以将消息移到资源文件中,以简化L10N和I18N。
如果程序由于匹配错误而终止但没有文件错误,则返回
EXIT_SUCCESS
是否有意义?我不确定是否应该返回EXIT_FAILURE
,即使文件无法打开也已经返回EXIT_FAILURE
。考虑在UNIX环境中的用法。任何错误都应返回q4312079q,以便使用命令强制脚本正确停止(或不继续输入无效内容)。
评论
\ $ \ begingroup \ $
混合使用0、1,{,[和(虽然值确实可以工作,但感觉有些黑。
\ $ \ endgroup \ $
– 200_success
2014年1月31日上午9:22
\ $ \ begingroup \ $
hackish表示C。是。如果问题更复杂,那我就不会这样做。但是,在这样一个琐碎的问题上变得毫无意义。
\ $ \ endgroup \ $
–马丁·约克
2014年1月31日上午9:28
\ $ \ begingroup \ $
@LokiAstari:我只是再次看了一下,除了if(action == 0)以外,我几乎理解了它。有什么意义吗?它与任何预设的错误条件都不对应,也不是通过添加非括号字符触发的(这是我根据外观尝试过的)。
\ $ \ endgroup \ $
– Jamal♦
2014年4月26日在1:26
\ $ \ begingroup \ $
@Jamal:如果(action == 0)是默认操作。如果不是打开或关闭(所以是最常见的情况)。大多在文档中说“如果我们看不到任何预期的情况,那就什么也不做”
\ $ \ endgroup \ $
–马丁·约克
2014年4月26日在3:31
\ $ \ begingroup \ $
@AkshatAgarwal:将所有位置初始化为0;
\ $ \ endgroup \ $
–马丁·约克
16年5月29日在3:47
#4 楼
让我们回答您首先遇到的问题:为此可以使用堆栈。您需要跟踪遇到的令牌的顺序,以LIFO方式删除它们-几乎是堆栈的定义。
std::map
对于3个代币而言是多余的。我将它们存储起来,但是存储在一个(gasp!)普通堆栈分配的数组中。由于std::map
的实现方式,其查找常数要比普通阵列高得多。当然,处理大量数据时,可以使用O(log n)
而不是O(n)
进行搜索,但是,交叉点很可能在几千中某个位置(取决于所有因素)。速度在这里并不是很重要(不是),但是搜索起来反而会更简单。我认为在一个函数中具有错误功能是完全可以的。将事物拆分成单独的功能是一件好事,尽管如此简单,但我发现没有什么意义。实际上,在函数中再次输出错误是可以的-它具有此时所需的所有信息。如果改为在
main
中处理此问题,则必须找出一个返回值,该返回值要么为“这就是我所期望的,这是我发现的,但是有一个错误”,或者“一切正常”。 就我个人而言,如果一切都很好(一切都正确匹配),我认为应该返回
EXIT_SUCESS
,否则返回EXIT_FAILURE
。考虑在某种管道中使用此方法,在该管道中文件必须经过一堆又一堆的检查-然后,您希望它在继续之前没有任何错误。传达一切的最佳方式是什么?使用EXIT_SUCCESS
。好吧,还有其他一些建议:
保留令牌列表(作为简单数组):
constexpr char tokenList[] = {'{', '(', '['};
然后可以将
pushOpeningBrackets
更改为:void pushOpeningBrackets(Brackets& opening, char ch)
{
for(char tok : tokenList)
{
if(ch == tok)
opening.push(tok);
}
}
这样做还有一个好处,就是如果您想添加另一个令牌,只需要将其添加到您的
tokenList
中,而不必添加另一个else if
语句。一个地方可以使用
map
(为简单起见)在errorsFound
中,,您需要关联的带有开括号的闭合大括号:
const std::map<char, char> closing = {{'{', '}'}, {'(', ')'}, {'[', ']'}};
bool errorsFound(Brackets& stack, char openingBracket, char closingBracket)
{
if(stack.empty())
{
std::cout << "Unmatched " << closingBracket;
return true;
}
char topBracket = stack.top();
stack.pop();
if(topBracket != openingBracket) {
auto expectedClosing = closing.find(topBracket);
std::cout << "Expected " << expectedClosing->second << " but found "
<< closingBracket;
return true;
}
return false;
}
也可以用来简化检查
main
的底部以类似的方式。#5 楼
您的typedef
typedef std::stack<char> Brackets;
…对我来说似乎很尴尬。
如果没有
typedef
,则您的堆栈可能已声明为std::stack<char> brackets;
但是,对于
typedef
,最终却是Brackets stack
,不幸的是倒退了,因为我希望stack
是类型而不是变量名。评论
\ $ \ begingroup \ $
是的,我对命名有点草率。我会回去修复其中的一些问题。
\ $ \ endgroup \ $
– Jamal♦
2014年1月31日上午8:35
#6 楼
首先,要使其真正有用,请将您解析的原始行添加到混合文件中。这样,用户不必手动手动扫描可能很大的文档。我想说的是,您应该将错误检查放到一个函数中。我也强烈建议保持跟踪错误的位置。手动解析一个很长的行以解决一个微小的错误,没有什么比这更有趣了,就像解析一个特定行的巨大文件一样。
typedef std::pair<std::string, size_t> lineNumber
std::vector<lineNumber> readFile(const std::string &fileName) {
std::ifstream fileStream(fileName.c_str(), std::ios::in);
if (fileStream.fail()) {
throw std::runtime_error("Cannot open file " + fileName + "\n");
}
std::vector<lineNumber> lines;
std::string temp;
while (getline(fileStream, temp)) {
lines.push_back(std::make_pair(temp, lines.size()+1));
}
if (lines.empty()) {
throw std::runtime_error("Empty file " + fileName + "\n");
}
fileStream.close();
return lines;
}
现在您可以添加自定义异常来告诉您的用户代码有什么问题,并使错误警告标准化。显然这可能是一个过大的杀伤力,但是我将其用于我编写的解析器中,自然会增加不同错误的数量。
void checkBrackets(const std::vector<lineNumber> &lines) {
std::map<char, char> bracketPairs= {std::make_pair(')', '('),
std::make_pair(']', '['),
std::make_pair('}', '{')};
std::stack<std::pair<char, size_t>> brackets;
for (lineNumber line : lines) {
for (auto it = line.first.begin(); it != line.first.end(); ++it) {
switch (*it) {
case '(':
case '[':
case '{':
brackets.push(std::make_pair(*it, std::distance(line.first.begin(), it)));
break;
case ')':
case ']':
case '}':
if (brackets.empty()) {
throw bracketException(MISSING_OPENING_BRACKET, line,
std::distance(line.first.begin(), it));
} else if (brackets.top().first != bracketPairs[*it]) {
throw bracketException(MISSING_CLOSING_BRACKET, line,
brackets.top().second);
}
brackets.pop();
break;
default:
continue;
}
}
if (!brackets.empty()) {
throw bracketException(MISSING_CLOSING_BRACKET, line,
brackets.top().second);
}
}
}
评论
由于这是由“所有其他字符均被忽略”所覆盖的,所以我只想评论一下,在不需要匹配的情况下(例如,用引号引起来或在注释中),匹配方括号的语言会使用它们。这可能是一个不错的扩展。至少必须处理字符串和char文字。请注意,由于文字的原因,您的代码几乎肯定无法验证自己。