0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何利用ChatGPT快速实现一个控制台进度条小工具?

CPP开发者 来源:CppMore 2024-01-18 13:41 次阅读

本篇讲解如何利用 ChatGPT 快速实现一个控制台进度条小工具,相比单纯介绍某些特性,此种方式涉及知识的综合运用,也顺便谈谈如何结合 AI 进行编程

问题描述

控制台程序执行一些耗时任务时,需要向用户显示当前任务执行的进度,以提供清晰的感知。比如一个下载程序,通过进度条便能告知用户当前的下载进度。

进度条可以单独显示,也可以在程序输出的最下方显示,下图是一个示例。

这是一种单控制条需求,执行任务,显示进度,输出流依旧是从上至下依序进行,适合单线程的场景。

多控制条显示的效果如下图,实现要更加复杂一些,本文暂时不会涉及该部分。

e338dba2-b5b5-11ee-8b88-92fbcf53809c.gif

初步分析

控制台上显示的这种符号,称为 ASCII Art,就是以字符构建的某种图案,不借助图片,也能够有一个生动的展示效果,比如下图这种。

e36393e2-b5b5-11ee-8b88-92fbcf53809c.png

因此控制台进度条也称为 ASCII Progress Bar,通过字符图案来模拟进度条的显示,通常分为已完成部分及未完成部分,使用两个字符,动态改变字符数量,便能够模拟出一个进度条。

模拟方式既定,下一问题在于进度条刷新。如果每更新一次进度,便输出一个字符图案,那么屏幕上将满是进度条,需要针对一条进度条,不断刷新其数据,而非每次都输出一条新的。具体实现时,便需要寻找定位进度条的方法,每次清除当前数据,重新打印新的数据,视觉上显示的是连续动画。

刷新思路亦成,接着的问题在于如何在进度条之上插入其他输出。进度条始终显示在用户输出下方,因此每次用户输出时,可以立即定位到进度条,定位之后清除当前进度条,输出用户内容,再重新打印进度条,便能够达到这一效果。

细枝末节,便需依赖具体的实现手法。

借助 ChatGPT 快速构建基本代码

需求明确,思路既定,接着便要着手设计库的结构和细节,实现细节这部分代码无需从零编写,可以借助 AI 快速生成。

我们所需做的,就是详细描述需求,以及预想的思路,让 ChatGPT 生成代码,验证是否符合需求,若不符合,纠正错误,让它再次生成,不断重复这个过程,直到基本满足期待的效果。如果一开始的效果就完全牛头不对马嘴,那也可以让它基于 Python 生成,等到效果尚可,再让它把代码转换成 C++ 代码。

经过多次调教,最终生成的代码如下:

 1#include
 2#include
 3#include
 4
 5voidprint_progress_bar(intiteration,inttotal,intbar_length=50){
 6floatprogress=static_cast(iteration)/total;
 7intarrow_length=static_cast(bar_length*progress);
 8intspaces=bar_length-arrow_length;
 9
10std::cout<< "
Progress: [";
11    for (int i = 0; i < arrow_length; ++i) {
12        std::cout << "-";
13    }
14    for (int i = 0; i < spaces; ++i) {
15        std::cout << " ";
16    }
17    std::cout << "] " << progress * 100 << "%" << std::flush;
18}
19
20int main() {
21    int total_iterations = 100;
22    for (int i = 0; i <= total_iterations; ++i) {
23        // 输出其他内容
24        std::cout << "�33[2K
Some other output " << i << std::endl;
25
26        // 更新并输出进度条
27        print_progress_bar(i, total_iterations);
28        std::cout << std::flush;
29
30        // 模拟任务执行时间
31        std::sleep_for(std::milliseconds(100));
32    }
33
34    // 输出一个换行来防止进度条下一行被覆盖
35    std::cout << std::endl;
36
37    return 0;
38}

生成的最终代码基本满足需求,能够按预期显示进度条。

对于进度条,生成的代码使用 - 表示完成部分, 表示未完成部分,每隔 100ms 刷新一次进度数据。进度条长度固定为 50,依据当前进度和控制条长度,动态计算已完成和未完成字符长度,通过循环打印出来。

而刷新显示,这也是调教时最麻烦的一个,起初生成的代码一直会不断打印控制条,最终告诉它使用 ANSI escape codes 才纠正为 �33[2K 。

这个转义码可以分为两部分,�33[2K 和 。后者比较常见,就是将光标移动到当前行的开头,而前者的作用是清除当前光标所在行,其实包含三个参数,意义分别为:

0K:清除从光标所在位置到行尾的内容;

1K:清除从行首到光标所在位置的内容;

2K:清除整行内容。

组合起来,作用是每次输出用户内容(用户内容最后需要换行,否则最后一行内容可能会被清除)时,先清除最后一行内容,再将光标移至行首,达到的效果就是清除当前进度条并回到行首。如果仅仅将光标移至行首,而不清除当前行,后面打印的内容若是比原有内容(进度条)长度短,便会留下原有内容的残余部分,致界面混乱。

用户的最后一行输出是换行符,生成代码利用 将光标移至行首,再输出进度条,其实完全多余,光标本来就在行首,每次打印的进度条本身就在用户内容的下方。

封装成类,微调代码

ChatGPT 生成的代码,虽然能够达到效果,但最多只有六十分,只是快速实现细节,省些力气而已,仍需要进一步微调。

构建一个 cpp-progress-bar 项目,以 Header-only 的形式添加一个 progress_bar 类,将变化点全部封装起来。

首先,将所有可定制的数据,全部抽离出来。比如控制条长度、数据长度、已完成字符和未完成字符等等。

 1classprogress_bar{
 2public:
 3progress_bar(inttotal,intbar_length=50,std::ostream&os=std::cout)
 4:m_bar_length{bar_length}
 5,m_data_length{total}
 6,m_done_char{'#'}
 7,m_undone_char{'.'}
 8,m_opening_bracket{'['}
 9,m_closing_bracket{']'}
10,m_os{os}
11,m_desc{"Progress"}
12{}
13
14autobar_length(intlen)->void{
15m_bar_length=len;
16}
17
18autobar_length()const->int{
19returnm_bar_length;
20}
21
22//...
23
24private:
25intm_bar_length;//控制条长度
26intm_data_length;//数据长度
27charm_done_char;//已完成字符
28charm_undone_char;//未完成字符
29charm_opening_bracket;//开括号
30charm_closing_bracket;//闭括号
31std::ostream&m_os;//输出流
32std::stringm_desc;//控制条描述信息
33};

其次,将「清除并回到行首」和「输出控制条」这两部分抽离出来,它们一个在用户内容之前输出,一个在之后输出,于是增加一个 before() 和 after() 接口来表示。

 1autoprogress_bar::before()const->void{
 2m_os<< "�33[2K
";
 3}
 4
 5auto progress_bar::after(int cur) const ->void{
 6autoprogress=static_cast(cur)/m_data_length;
 7autofinished_length=static_cast(progress*m_bar_length);
 8
 9m_os<< m_desc << ": " << m_opening_bracket;
10    for (int i = 0; i < finished_length; ++i) {
11        m_os << m_done_char;
12    }
13    for (int i = 0; i < m_bar_length - finished_length; ++i) {
14        m_os << m_undone_char;
15    }
16    m_os << m_closing_bracket << progress * 100 << "%" << std::flush;
17}

最后,以一个 update() 接口来调用以上这两个接口,更新进度条。

 1autoprogress_bar::update(intcur)const->void{
 2this->before();
 3this->after(cur);
 4}
 5
 6autoprogress_bar::update(intcur,std::invocableautofn)const->void{
 7this->before();
 8std::invoke(fn,cur);
 9this->after(cur);
10}

提供两个重载版本,以应对无用户输出时的变化性。

现在,便可以这样使用:

 1intmain(){
 2inttotal=50;
 3cpb::progress_barprogress(total);
 4for(autoi:std::iota(0,total)){
 5//Updatetheprogressbar
 6progress.update(i+1,[](intv){
 7std::cout<< "Some other output " << v << '
';
 8        });
 9        std::sleep_for(std::milliseconds(100));
10    }
11}

这便是一个轻量级库的雏形。

优化实现

雏形已成,但实现方面依旧是 ChatGPT 的实现,尽管只有几行代码,生成的代码还是相当丑陋而低效。到这一步,便要开始替换生成的实现。

也就是说,ChatGPT 生成的代码仅仅是能跑,雏形交给它来快速生成,后续的封装和优化工作则全由自己来做,替换所有低效实现。

当前留下的生成代码只有生成已完成字符和未完成字符,这部分可以使用 C++20 Fomatting Library 进行优化,简化代码。

1autoprogress_bar::after(intcur)const->void{
2autoprogress=static_cast(cur)/m_data_length;
3autofinished_length=static_cast(progress*m_bar_length);
4
5autoprogress_info=std::format("Progress:[{3:>3}%][{0:#^{1}}{0:.^{2}}]","",finish_length,bar_length-finish_length,static_cast(progress*100));
6m_os<< progress_info << std::flush;
7}

通过优化,核心代码只剩下一行,这就是 fmt 库的强大所在。

但是,格式化时没有动态指定填充字符,这是因为 fmt 暂时不支持这种代码:

1//"====="
2std::cout<< std::format("{:=^{}}
", "", 5); // OK
3   std::cout << std::format("{0:{1}^{2}}
", "", '-', 5); // Error

由于会产生复杂的开销,默认的 std::formatter 并不支持动态指定填充字符。

可以通过定制来手动实现,代码如下:

 1namespacecpb{
 2
 3structfill{
 4charvalue;
 5intwidth;
 6};
 7
 8}//namespacecpb
 9
10
11template<>
12structstd::formatter{
13constexprautoparse(format_parse_context&ctx){returnctx.begin();}
14
15autoformat(constcpb::fill&f,auto&ctx)const{
16returnstd::fill_n(ctx.out(),f.width,f.value);
17}
18};

别看仅有几行代码,fmt 的定制存在巨坑。

这种实现是错误的:

 1namespacecpb
 2{
 3structfill{
 4charvalue;
 5intwidth;
 6};
 7}
 8
 9template<>
10structstd::formatter{
11constexprautoparse(format_parse_context&ctx){returnctx.begin();}
12
13autoformat(constcpb::fill&f,auto&ctx){
14returnstd::fill_n(ctx.out(),f.width,f.value);
15}
16};

这种实现也是错误的:

 1namespacecpb
 2{
 3structfill{
 4charvalue;
 5intwidth;
 6};
 7}
 8
 9template<>
10structstd::formatter{
11autoparse(format_parse_context&ctx){returnctx.begin();}
12
13autoformat(constcpb::fill&f,auto&ctx)const{
14returnstd::fill_n(ctx.out(),f.width,f.value);
15}
16};

这种实现还是错误的:

 1namespacecpb
 2{
 3structfill{
 4charvalue;
 5intwidth;
 6};
 7}
 8
 9template<>
10structstd::formatter{
11constexprautoparse(format_parse_context&ctx){returnctx.begin();}
12
13autoformat(constcpb::fill&f,format_parse_context&ctx)const{
14returnstd::fill_n(ctx.out(),f.width,f.value);
15}
16};

这种实现依旧是错误的:

 1structfill{
 2charvalue;
 3intwidth;
 4};
 5
 6template<>
 7structstd::formatter{
 8constexprautoparse(format_parse_context&ctx){returnctx.begin();}
 9
10autoformat(constfill&f,auto&ctx)const{
11returnstd::fill_n(ctx.out(),f.width,f.value);
12}
13};

尤其是最后一个,因为标准存在 std::fill,此时特化 std::formatter 中使用的 fill 是 std::fill,而不是自己定义的 fill,所以必须将其置入命名空间内。

由此也可见,基于特化的这种定制方式非常不友好,出现错误较为莫名其妙。

有了这个定制,便可以使用 fill 来进一步简化原有实现:

1autoafter(intcur)const->void{
2autoprogress=static_cast(cur)/m_data_length;
3autofinished_length=static_cast(progress*m_bar_length);
4
5autoprogress_info=std::format("{}:[{:>3}%]{}{}{}{}",m_desc,static_cast(progress*100),m_opening_bracket,fill{m_done_char,finished_length},fill{m_undone_char,m_bar_length-finished_length},m_closing_bracket);
6m_os<< progress_info << std::flush;
7}

现在这个实现便精简多了。

AOP 优化

当前接口存在 before() 和 after(),这不正是 AOP 要解决的问题,我们几年前也使用 C++20 实现过一个轻量级的 AOP 库,此处可以基此稍微扩展一下,满足当前需求。

于是实现进一步简化为:

1autoupdate(intcur)const->void{
2aopcxx::make_aspect(this,cur);
3}
4
5autoupdate(intcur,std::invocableautofn)const->void{
6aopcxx::make_aspect(this,fn,cur);
7}

原理几年前便已讲过,扩展的源码可以自行去看。

示例

至此,三两下库已成型,可以这样使用:

 1intmain(){
 2inttotal=50;
 3cpb::progress_barprogress(total);
 4for(autoi:std::iota(0,total)){
 5//Updatetheprogressbar
 6progress.update(i+1,[](intv){
 7std::cout<< "Some other output " << v << '
';
 8        });
 9        std::sleep_for(std::milliseconds(100));
10    }
11
12    std::cout << "
";
13    progress.done_char('X');
14    progress.undone_char('-');
15    for (auto b : std::iota(0, total)) {
16        progress.update(b + 1);
17        std::sleep_for(std::milliseconds(100));
18    }
19}

如果想亲自测试一下代码或查看源码,可以通过以下指令:

1gitclonehttps://github.com/lkimuk/cpp-progress-bar.git
2mkdirbuild&&cdbuild
3cmake..
4make
5./test

总结

思路想法确定,借助 ChatGPT 快速生成代码雏形,能够加快开发速度,让你避开细枝末节,快速让目标运行起来。

在此基础上,自己只需专注代码优化,将精力放在核心功能上。

尽管后期可能会替换掉 AI 生成的所有实现,但也要事半功倍。先快速让程序跑起来,再去优化局部细节,比完全从局部细节构建起整体结构,要高效得多。

大家可以尝试使用起来。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • ASCII
    +关注

    关注

    5

    文章

    172

    浏览量

    35100
  • C++语言
    +关注

    关注

    0

    文章

    147

    浏览量

    6990
  • python
    +关注

    关注

    56

    文章

    4796

    浏览量

    84668
  • ChatGPT
    +关注

    关注

    29

    文章

    1560

    浏览量

    7641

原文标题:借助 ChatGPT 快速实现一个轻量级的控制台进度条库

文章出处:【微信号:CPP开发者,微信公众号:CPP开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何使用Github上的advcpmv来实现Linux中的cp和mv命令的进度条

    由于 cp 和 mv 命令都是属于 coreutils 工具包下的,因此我们的主要操作就是在编译 coreutils 的时候加入补丁从而实现进度条功能。
    的头像 发表于 07-23 08:27 1794次阅读

    如何显示vi程序运行的进度条

    程序调用mtalab脚本节点,运行很慢,想设计进度条,实时显示运行进度,求各位大神帮忙
    发表于 04-01 13:53

    labview的进度条

    这是labview的进度条程序,比较好用
    发表于 08-04 14:30

    求教利用Labview和其它语言混合编程如何设计弧形进度条

    1.利用Labview和其它语言混合编程如何设计弧形进度条?2.求自定义控件Demo
    发表于 03-10 18:25

    labview进度条

    我用labview2017做了文件解压和复制的vi,解压过程中不知道真实的解压进度,怎么才能做一个真实的进度条,要真是的,不是自己规定的
    发表于 04-26 09:10

    labview实现进度条

    进度条
    发表于 03-25 17:06

    怎么设置进度条

    RT!比如 我创建随意长度的进度条然后我知道文件的大小 当把这个文件里的数据读完后进度条
    发表于 08-22 04:35

    HarmonyOS实战——ProgressBar进度条组件基本使用

    ProgressBar案例——点击进度条增加实际进度值需求分析:每单击进度条组件时,进度条就加 5% 的
    发表于 09-22 23:31

    分享开源的ESP32 物联网小工具

    描述ESP32 物联网小工具这个小工具是学习多种技术和技能的机会。这是摘要:了解如何使用 PlatformIO 和 Microsoft Visual Studio Code 对 E
    发表于 06-17 10:03

    自写小工具

    自己写的小工具感觉还不错,分享给大家。
    发表于 05-17 09:49 40次下载

    射频工程类计算小工具

    射频工程类计算小工具,有LC谐振频率计算、PCB特性阻抗计算工具软件、电感量计算等共19计算小工具
    发表于 03-21 14:50 54次下载

    电阻分压计算小工具

    计算电阻分压的小工具
    发表于 09-07 14:54 41次下载

    教你在Linux上写进度条小程序

    在 Linux 上写下一个简易的进度条小程序。
    的头像 发表于 01-29 11:37 1287次阅读

    介绍两种LabVIEW里实现进度条的方式

    进度条,是非常重要的UI元素。
    的头像 发表于 07-14 09:29 8116次阅读
    介绍两种LabVIEW里<b class='flag-5'>实现</b><b class='flag-5'>进度条</b>的方式

    【AWTK使用经验】如何设计立体电池进度条

    AWTK是基于C语言开发的跨平台GUI框架。《AWTK使用经验》系列文章将介绍开发AWTK过程中些常见问题与解决方案,例如:如何加载外部资源?如何设计自定义进度条?这些都会在系列文章进行解答
    的头像 发表于 04-18 08:25 455次阅读
    【AWTK使用经验】如何设计立体电池<b class='flag-5'>进度条</b>?