【C++】改进后的狗屁不通文章生成器:优化字词前后关系

上网随意浏览,便会发现形形色色的怪异代码项目。比如这个【C++】的狗屁不通文章生成器2.0,挺有趣的是,它的更新历程或许能唤起许多程序员对过往糟糕代码的回忆。

曾经的不堪回首

作者起初对翻看自己过去的代码并无兴趣。那时,处理中文字符仿佛一场恶梦,带来无穷的困扰。程序模块混乱,可读性极差,宛如一个杂乱无章的旧仓库。回想起那段时间,自己都感到震惊,或许当时并未充分考虑为何要以那样的方式编写代码。这恰恰反映了新手程序员常遇到的问题,即初始阶段未能构建良好的代码结构。而且,那些看似不合理的编写方式,在日后回顾时往往让人懊悔不已。

class wordpair
{
private:
    string preword;            // 前缀
    map<string, int> sufwords; // 后缀,次数
    int count;                 // 总次数
public:
    wordpair(string pre);
    wordpair(string pre, string suf);
    wordpair(string pre, map<string, int> suf);
    ~wordpair();
    string getPreword() const;
    map<string, int> getSufwords() const;
    void setPreword(string pre);
    void setSufwords(map<string, int> suf);
    string toJson() const;
    void addSufword(string suf);
    string chooseSufword() const;
};

后来我渐渐明白,代码确实需要更新和改进。那天,无意中闲来无事,我决定动手对之前的混乱代码进行一番整理,希望将它改造成一个能让人看得过去的模样。

字词前后关系的改进

字词的前后关系处理至关重要。我们选择使用class进行构建,为了提升代码质量,实施了一系列改进措施。这些措施删除了数据中的多余信息,就如同给一个繁杂的身体做了抽脂手术一般。

class createArticle
{
private:
    vector<wordpair> wordpairlist;
    string article;
public:
    createArticle();
    ~createArticle();
    void importWords(string filename, int len_pre = 1, int len_suf = 1);
    void exportWords(string filename);
    void addWordPair(string pre, string suf);
    void generateArticle(string startword, int lenout = 10000);
    void printArticle(string filename);
};

此举显著增强了类别的封闭性,随后使用map来统计后缀出现的频次。这样一来,数据的结构层次更加分明,检索过程也变得简便许多。而记录所有后缀出现的总频次,则是为了在生成文章时选择合适的后缀提供依据。

文章生成系统的调整

之前将过多操作堆砌在main()函数中,实乃设计上的败笔。这样的设计显得过于杂乱,很容易导致忘记各部分的具体功能。因此,我明智地将文章生成功能抽象化,封装成了一个类。

它主要的工作内容包括记录所有字词对、生成文章的记录、文件流操作以及文章生成逻辑等方面。这就像是将各种工具分别放入各自的收纳箱,使用时便不会感到手忙脚乱。

部分重要函数的实现

string wordpair::toJson() const
{
    string str = """;
    str += this->preword + "" : {";
    for (auto &it : this->sufwords)
    {
        str += """ + it.first + """ + ":" + to_string(it.second) + ",";
    }
    str += "}";
    return str;
}

图片[1]-【C++】改进后的狗屁不通文章生成器:优化字词前后关系-东山笔记

里面的函数多数都很简单,我们只需关注几个关键的。比如以class为例,我们可以直接跳过那些基础函数,直接看重点内容。

图片[2]-【C++】改进后的狗屁不通文章生成器:优化字词前后关系-东山笔记

将数据转换为json格式的函数,旨在生成格式规范的词对。json格式在文本文件中结构清晰且易于理解。尽管这个函数在程序中并非核心,其主要功能是进行验证,但它也为后续操作奠定了基础。

接下来是那个添加后缀的函数,它有一套自己的运作逻辑。另外,还有负责选择后缀的函数,采用的是随机数的方法,就像抽奖转盘一样,从众多后缀词中随机挑选,这种方式相当直接。

void wordpair::addSufword(string suf)
{
    for (auto &it : this->sufwords)
    {
        if (it.first == suf)
        {
            it.second++;
            return;
        }
    }
    this->sufwords[suf] = 1; // if the word is not in the map, add it with a count of 1
}

class相关操作

在class中,可以进行文本分割和文章生成等操作。文本分割至关重要,需精确地将文本划分开来,就像精准地剪断绳子成几段。而生成文章,如前所述,采用随机数策略,虽然提供了一定的灵活性,但仍有诸多不足之处。

演示环节暴露的情况

string wordpair::chooseSufword() const
{
    if (this->sufwords.size() == 1)//如果只有一个后缀词就直接输出,减少算力负担
    {
        return this->sufwords.begin()->first;
    }
    else
    {
        // 随机选择一个后缀词
        random_device rd;
        ranlux48 engine(rd());
        uniform_int_distribution<> dist(0, this->count);//在类中定义了count,这里就省掉了遍历
        int random_number = dist(engine);//产生一个随机数
        std::string result;
        for (auto &it : this->sufwords)//抽奖
        {
            if (random_number < it.second)
            {
                result = it.first;
            }
            else
                random_number -= it.second;
        }
        return result;
    }
}

看演示结果,长度限制确实至关重要。以(3×2)为例,当启动词是“春天”时,效果便显现出来了。

vector<string> charlist = splitchar(filestr);//先将从文件读到的字符串分割
    string preword = "", sufword = "";
    for (int i = 0; i < charlist.size() - len_suf - len_pre; i++)//每次向后移动一个字符,进行切割
    {
        preword = "", sufword = "";
        for (int j = i; j < i + len_pre + len_suf; j++)
        {
            if (j - i < len_pre)
            {
                preword += charlist[j];//从第i个字符开始,到第i+len_pre个字符连接起来作为前缀
            }
            else
            {
                sufword += charlist[j];//从第i+len_pre个到字符开始,到第i+len_pre+len_suf个字符连接作后缀
            }
        }
        this->addWordPair(preword, sufword);//添加进wordpairlist
    }

这个版本确实解决了一些问题,比如中文乱码不再出现,中英文混合字符串也能正确处理。然而,它仍存在一个重大问题,那就是无法生成具有可读性的文章。这让我们不禁思考,要制作出一个能真正产出有意义文章的生成器,我们还有多长的路要走?这个问题值得我们深入讨论。大家是否也遇到过类似的情况,即优化改进后的代码项目仍然存在严重问题?欢迎点赞、分享,并在评论区展开讨论。

/*
startword——启动词
lenout——长度限制(避免无限循环)
*/
void createArticle::generateArticle(string startword, int lenout)
{
    this->article += startword;
    bool stop; // 加一个停止标志,当无法匹配到前缀时停止
    int prewordlen = this->wordpairlist.front().getPreword().length();
    int sufwordlen = this->wordpairlist.front().getSufwords().begin()->first.length();
    string lastword;
    for (int i = 0; i < lenout; ++i)
    {
        stop = true;
        if (this->article.length() >= prewordlen) // 如果文章长度大于词对中前缀词的长度,则直接拼接
        {
            lastword = this->article.substr(this->article.length() - prewordlen, prewordlen);//article最后的len_pre个字符,作为前缀
            for (auto &it : this->wordpairlist)
            {
                if (it.getPreword() == lastword)//通过lastword匹配词对
                {
                    this->article += it.chooseSufword();
                    stop = false;
                    break;
                }
            }
            if (stop)//遍历了一边词对的list没有匹配的词对时,退出循环
                break;
        }
        else//启动词长度小于词对前缀的情况,例如词对分割为3+2时,启动词长度为2,小于前缀长度3,无法正常拼接,于是走此处
        {
            lastword = this->article;
            for (auto &it : this->wordpairlist)//同上遍历
            {
                int position = it.getPreword().find(lastword);
                if (position != string::npos)
                {
                    this->article += (it.getPreword() + it.chooseSufword()).substr(position+lastword.length(), sufwordlen);//先将前后缀连接,再从匹配到的位置开始截取
                    stop = false;
                    break;
                }
            }
            if (stop)
                break;
        }
    }
}

© 版权声明
THE END
喜欢就支持一下吧
分享