【问题标题】:Text file as input in C++ program will not work unless the text is copy and pasted除非文本被复制和粘贴,否则作为 C++ 程序输入的文本文件将不起作用
【发布时间】:2020-02-27 01:29:59
【问题描述】:

我的代码中有一个非常奇怪的错误,有点难以解释。让我从程序的作用开始:基本上,C++ 程序获取输入文本(来自同一目录中名为“input.txt”的文件)并使用马尔可夫链生成一些类似于输入文本样式的人工输出文本并将其打印到终端。

当我将“爱丽丝梦游仙境”(http://paulo-jorente.de/text/alice_oz.txt) 的文本直接复制并粘贴到“input.txt”中时,它可以工作,但是如果我在文本内容的开头或结尾添加任何单词或字符文件,然后代码停止运行(或无限运行)。但是,如果我在文本文件内容中间的任何位置添加文本,则不会发生这种情况。

如果您想自己测试它,请尝试运行将爱丽丝梦游仙境复制到“input.txt”中的代码。然后在它运行成功后,转到 input.txt 并在 'Alice' 的最后一个文本之后键入一些随机字符或单词(“......再次回家!”)并尝试再次运行它;它会失败。

代码如下:

#include <ctime>
#include <iostream>
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
#include <map>
using namespace std;

class markovTweet{
    string fileText;
    map<string, vector<string> > dictionary;

public:

    void create(unsigned int keyLength, unsigned int words) {
        ifstream f("input.txt");
        if(f.good()){
          fileText.assign((istreambuf_iterator<char>(f)), istreambuf_iterator<char>());
        }else{
          cout << "File cannot be read. Ensure there is a file called input.txt in this directory." << "\n" << endl;
          return;
        }
        if(fileText.length() < 1){
          return;
        }
        cout << "\n" << "file imported" << "\n";
        createDictionary(keyLength);
        cout << "\n" << "createDictionary" << "\n" << "\n";
        createText(words - keyLength);
        cout << "\n" << "text created, done" << endl;
    }

private:

    void createText(int w) {
        string key, first, second;
        size_t next;
        map<string, vector<string> >::iterator it = dictionary.begin();
        advance( it, rand() % dictionary.size() );
        key = (*it).first;
        cout << key;
        while(true) {
            vector<string> d = dictionary[key];
            if(d.size() < 1) break;
            second = d[rand() % d.size()];
            if(second.length() < 1) break;
            cout << " " << second;
            if(--w < 0) break;
            next = key.find_first_of( 32, 0 );
            first = key.substr( next + 1 );
            key = first + " " + second;
        }
        cout << "\n";
    }

    void createDictionary(unsigned int kl) {
        string w1, key;
        size_t wc = 0, pos, next;
        next = fileText.find_first_not_of( 32, 0 );
        if(next == string::npos) return;
        while(wc < kl) {
            pos = fileText.find_first_of(' ', next);
            w1 = fileText.substr(next, pos - next);
            key += w1 + " ";
            next = fileText.find_first_not_of(32, pos + 1);
            if(next == string::npos) return;
            wc++;
        }
        key = key.substr(0, key.size() - 1);
        while(true) {
            next = fileText.find_first_not_of(32, pos + 1);
            if(next == string::npos) return;
            pos = fileText.find_first_of(32, next);
            w1 = fileText.substr(next, pos - next);
            if(w1.size() < 1) break;
            if(find( dictionary[key].begin(), dictionary[key].end(), w1) == dictionary[key].end() ) 
                dictionary[key].push_back(w1);
            key = key.substr(key.find_first_of(32) + 1) + " " + w1;
        }
    }
};

int main() {  
    markovTweet t;
    cout << "\n" << "Artificially generated tweet using Markov Chains based off of input.txt: " << "\n" << "\n";
    //lower first number is more random sounding text, second number is how long output is.
    t.create(4, 30);
    return 0;
}

这是一个非常奇怪的错误,非常感谢您提供的任何帮助!谢谢!

【问题讨论】:

  • 有很多代码需要查看。我会在调试器中戳它,看看它卡在哪里。
  • 当您使用调试器运行程序时,您看到了什么?这就是调试器的用途。如果您不知道如何使用调试器,这是一个学习如何使用调试器一次运行一行的好机会,监控所有变量及其值的变化,并分析程序的逻辑执行流程。了解如何使用调试器是每个 C++ 开发人员必备的技能,没有例外。在调试器的帮助下,您应该能够快速找到此程序以及您编写的所有未来程序中的所有错误,而无需向任何人寻求帮助。
  • 这是一个学习构建minimal complete example 的好机会,这是一项至关重要的编程技能。您可以用爱丽丝梦游仙境的整个文本重现该错误吗?尝试删除一些文本;错误还在吗?你能把它简化成一行吗?删除一些代码怎么样——别管马尔可夫模型或输出,只需尝试用更简单的程序重现错误。您可能会在此过程中自己发现错误,但无论如何您都会有一个更简单的案例要发布。
  • 感谢 tadman 和 Sam 的 cmets。我在 Repl.it 中运行代码,但我转向 Visual Studio 尝试调试它。当我运行本地 Windows 调试器时,代码只是无限运行。我是 C++ 编码的新手 - 有没有更好的调试方法?
  • 在我看来,正在使用的编辑器正在向字符集添加字节顺序标记或其他一些更改,从而导致文本添加出现问题。我不知道为什么在中间插入不会触发重新格式化,同时添加到开头或结尾完成 - 这可能取决于相关编辑器跟踪更改的方式。我会在更改前后对文件进行十六进制转储,以查看是否仅存在您所做的更改,或者是否还没有发生其他格式或字符集更改。

标签: c++ text markov-chains markov


【解决方案1】:

这可能是关于std::map 的时间复杂度与operator[]() 的考虑。

使用 operator[] : “[]” 也可以用于在 map 中插入元素。类似于上面的函数并返回指向新构造元素的指针。不同之处在于此运算符始终构造一个新元素,即即使值未映射到键,也会调用默认构造函数并将“null”或“empty”值分配给键。地图的大小总是增加 1。 时间复杂度:log(n) 其中 n 是地图的大小


来自:geeksforgeeks



在您班级的 createDictionary() 函数中,尝试在 2nd while 循环中添加这行代码:

{
    //...code 
    if (find(dictionary[key].begin(), dictionary[key].end(), w1) == dictionary[key].end()) {
          dictionary[key].push_back(w1);
          std::cout << dictionary.size() << std::endl;
    //code...
}

当我从文件中复制文本时,它会在您的字典或哈希图中生成 62037 个条目。运行和完成大约需要 20 - 30 秒。

当我在文件末尾添加文本“Good Bye!”时,将其保存并运行程序/调试器,它生成了 62039 个条目。再次运行大约需要 20-30 秒。

然后我将文本“Hello World”添加到文件的开头,保存并运行程序/调试器,它生成了 62041 个条目。再次运行大约需要 20-30 秒。

但是,在此过程中有几次,它在您的地图中生成了许多条目,但代码仍在循环中……有一次大约是 620xx - 640xx。我不知道是什么导致它生成这么多键......但就像我说的那样,有几次它退出打印值,但仍在迭代相同的 while 循环,但地图没有增加...

这发生在我第一次在文件开头输入文本时,在尝试使用末尾附加的文本之后。这是当我决定打印出你的地图的大小并注意到我得到这个无限循环的时候......然后我停止调试器回到文本文件并在开头保留插入的文本,但删除了附加的文本最后确保在文本末尾留一个空格。

这一次,当我运行程序/调试器时,它工作正常并生成了 62039 个条目。再次运行大约需要 20-30 秒。之后,在开头插入文本的第一次成功运行是当我在末尾添加文本时,它运行良好。然后我什至尝试了“Hello World!”后跟一个换行符,方法是使用 enter 进入文本文件并具有“再见!”前面也有一个,它仍然可以正常工作。


是的,有一些东西导致了错误,但我不知道究竟是什么导致了它。但是,我相信我已经追踪到它在这个 while 循环和退出的条件分支中......它应该已经打破了这个循环并进入了 createText 函数但它从未爆发过,条件你有:

if (next == std::string::npos) return

if (w1.size() < 1) break;

不知何故没有得到满足。


时间复杂度还可以,但是,它不是最好的,但也不是最差的,因为在 O(log n) 时间运行大约 62-63k 个条目。这也不包括计算需要考虑的空间复杂度。


可能是在一次运行期间,您可能会遇到导致无限循环的堆栈溢出,而在下次运行时,可能不会。我认为这与直接将文本添加到文本文件中没有任何关系,只是它会增加 O(log N) time 中地图的大小并增加空间复杂度。

无论您在此文本文件中添加了什么内容,在保存后,您的程序或算法的编写方式如何,它都会通过迭代器类按字符类型将该文件的所有内容作为指针索引拉取,并将其存储到一个字符串,fileText。构造此字符串后,您的班级成员字符串中大约有 336940 个字符。


希望这些信息可以指导您缩小程序中的错误位置并确定实际导致错误的原因。确实很难缩小这个罪魁祸首。

【讨论】:

  • 写得很好的代码解释和演练!你的见解很有用,谢谢!在尝试了多种解决方法和编辑后,我添加了一行代码,在导入的文本末尾附加了一个空格(就像你提到的那样),现在它每次都能正确运行。感谢您的帮助和分析
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-23
  • 1970-01-01
  • 2014-08-16
  • 2017-02-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多