使用 C++23 协程实现第一个 co_yield 同步风格调用接口--Qt计算排列组合

上一篇介绍了 co_await 的例子。与 co_await 类似,在C++23的协程特性里, co_yield 用于从协程执行过程中暂停,并返回值。这个功能乍一听起来很奇怪,网上的例子大多是用一个计数器来演示多次中断协程函数,返回顺序的计数值。这看起来毫无意义。

其实这个功能主要想演示的就是协程 co_yield 具备打断一个函数的执行,并多次返回值的能力。这种能力允许实现一种隐式状态机,每次使用时,返回下一个状态。这对于极为复杂的状态计算来说,是很有用的。它(协程)避免了显式的设置状态记忆句柄,大大简化了实现难度。同时,由于可以任意打断执行,便于在中间获取、展示一些数据状态、甚至单步调试,对构造一些教学程序意义重大。典型的是观察堆排序的中间态,不需要大幅度修改排序算法插入很多的printf,而是在函数外部做。

我们以产生任意P(N,M)、C(N,M)这样的排列、组合数序列为例子,看看传统算法和协程的区别。

1. 回溯法迭代排列组合

传统的回溯法,求取一个排列的算法如下:

void pnm_calc(const int n, const int m)
{std::vector<int> vec_buf,vec_bz;int swim = 0;bool finished = false;for (int i=0;i<m;++i)    vec_buf.push_back(0);for (int i=0;i<n;++i)    vec_bz.push_back(0);do{int ch = 0;if (swim<m){while (vec_bz[ch])++ch;vec_buf[swim] = ch;vec_bz[ch] = 1;++swim;}if (swim==m){//打印for (int i=0;i<m;++i)printf("%d,",vec_buf[i]);printf("\n");bool hit = false;do{if (swim<m && swim >=0) vec_bz[vec_buf[swim]] = 0;--swim;if (swim>=0){int nextv = vec_buf[swim];do{++nextv;if (nextv >=n)break;if (vec_bz[nextv]==0)hit = true;} while (hit == false);if (hit==true){vec_bz[vec_buf[swim]] = 0;vec_buf[swim] = nextv;vec_bz[nextv] = 1;++swim;}}elsefinished = true;} while (finished == false && hit == false);}}while(finished == false);
};
int main(int argc, char *argv[])
{pnm_calc(4,3);return 0;
}

输出:

0,1,2,
0,1,3,
0,2,1,
0,2,3,
...
3,1,2,
3,2,0,
3,2,1,

2 传统状态机封装

上述打印显示结果演示的是回溯法本身。若为了更好地使用组合数,需要对算法进行封装,以便于批量的获取、运用组合数的各组结果。比如考虑到总数可能很大,需要分批次返回结果等功能,显著增加了工作量。

#include <vector>
#include <cstdio>
struct tag_NM_State
{std::vector<unsigned short> vec_buf;std::vector<unsigned short> vec_bz;int swim;bool finished;
};
/*!\brief pnm 快速算法,使用带有记忆效应的 tag_NM_State 记录穷尽进度很好的避免了重新计算的耗时\fn pnm\param n				N,集合数\param m				M, 子集\param vec_output		存储结果的集合,本集合会自动增长\param state			状态存储\param limit			本次最多样本数\return int			本次给出的样本数*/
int pnm(int n, int m, std::vector<std::vector <unsigned short> > & vec_output,tag_NM_State * state, int limit/* = 0*/)
{std::vector<unsigned short> & vec_buf = state->vec_buf,& vec_bz = state->vec_bz;int &swim = state->swim;bool &finished = state->finished;const bool firstRun = vec_output.size()?false:true;if (vec_bz.size()==0){for (int i=0;i<m;++i)    vec_buf.push_back(0);for (int i=0;i<n;++i)    vec_bz.push_back(0);swim = 0;finished = false;}if (finished==true)return 0;int group = 0;do{int ch = 0;if (swim<m){while (vec_bz[ch])++ch;vec_buf[swim] = ch;vec_bz[ch] = 1;++swim;}if (swim==m){if (!firstRun)memcpy(vec_output[group].data(),vec_buf.data(),m*sizeof(unsigned short));elsevec_output.push_back(vec_buf);++group;bool hit = false;do{if (swim<m && swim >=0) vec_bz[vec_buf[swim]] = 0;--swim;if (swim>=0){int nextv = vec_buf[swim];do{++nextv;if (nextv >=n)break;if (vec_bz[nextv]==0)hit = true;} while (hit == false);if (hit==true){vec_bz[vec_buf[swim]] = 0;vec_buf[swim] = nextv;vec_bz[nextv] = 1;++swim;}}elsefinished = true;} while (finished == false && hit == false);if (group>=limit && limit>0)break;}}while(finished == false);return group;
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);using std::vector;tag_NM_State state;const int n = 4, m = 3, group = 10;vector<vector<unsigned short> > result;int ret = pnm(n,m,result,&state,group);while (ret>0){printf("\ngroup contains %d results:\n",ret);for (int i=0;i<ret;++i){printf("\n\t");for (int j=0;j<m;++j)printf("%d ",result[i][j]);}ret = pnm(n,m,result,&state,group);}printf("\nFinished\n");return 0;
}

分批输出:


group contains 10 results:0 1 20 1 30 2 10 2 30 3 10 3 21 0 21 0 31 2 01 2 3
group contains 10 results:1 3 01 3 22 0 12 0 32 1 02 1 32 3 02 3 13 0 13 0 2
group contains 4 results:3 1 03 1 23 2 03 2 1
Finished

详细算法参考 https://goldenhawking.blog.csdn.net/article/details/80037669

3. 协程封装

使用C++23 协程后,使用变得非常简洁:

int main(int argc, char *argv[])
{const int n = 4 , m = 3;nmCalc pnm = pnm_calc(n,m);while (pnm.next()){const int * res = pnm.currResult();printf("\n\t");for (int j=0;j<m;++j)printf("%d ",res[j]);}
}

每次调用 pnm.next() 就返回下一组结果且无需记忆状态。

但这也是有代价的!为了达到上述的效果,协程封装如下:

#ifndef NMCALC_H
#define NMCALC_H
#include<coroutine>
#include<vector>
class nmCalc
{
public:struct promise_type {//记录本次排列组合的结果const int * m_currResult;auto get_return_object() { return nmCalc{ handle::from_promise(*this) }; }auto initial_suspend() { return std::suspend_always{}; }auto final_suspend() noexcept { return std::suspend_always{}; }void unhandled_exception() { return ;}void return_void(){}auto yield_value(const int *  result ) {this->m_currResult=result; return std::suspend_always{}; }};using handle = std::coroutine_handle<promise_type>;
private:handle hCoroutine;nmCalc(handle handle) :hCoroutine(handle) {}
public:nmCalc(nmCalc&& other)noexcept :hCoroutine(other.hCoroutine) { other.hCoroutine = nullptr; }~nmCalc() { if (hCoroutine) hCoroutine.destroy(); }//请求下一组结果,调用后 co_yield继续。bool next() const { return hCoroutine && (hCoroutine.resume(), !hCoroutine.done()); }const int *  currResult() const { return hCoroutine.promise().m_currResult; }
};nmCalc pnm_calc(const int n, const int m)
{std::vector<int> vec_buf,vec_bz;int swim = 0;bool finished = false;for (int i=0;i<m;++i)    vec_buf.push_back(0);for (int i=0;i<n;++i)    vec_bz.push_back(0);do{int ch = 0;if (swim<m){while (vec_bz[ch])++ch;vec_buf[swim] = ch;vec_bz[ch] = 1;++swim;}if (swim==m){//返回一组结果!!!!!co_yield vec_buf.data();bool hit = false;do{if (swim<m && swim >=0) vec_bz[vec_buf[swim]] = 0;--swim;if (swim>=0){int nextv = vec_buf[swim];do{++nextv;if (nextv >=n)break;if (vec_bz[nextv]==0)hit = true;} while (hit == false);if (hit==true){vec_bz[vec_buf[swim]] = 0;vec_buf[swim] = nextv;vec_bz[nextv] = 1;++swim;}}elsefinished = true;} while (finished == false && hit == false);}}while(finished == false);
};

4. 体会与思考

这种封装方式,显著提高了算法流程的紧凑程度。无需考虑如何巧妙的保留状态,而是直接借助协程随时打断并返回。

这在算法极其复杂的情况下,尤其有效。同时,对于单步演示,比如按一下按钮出一次,也很方便,主要代码参考:

https://gitcode.net/coloreaglestdio/qtcpp_demo/-/tree/master/qt_coro_test

运行效果:

co_yield

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2811884.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

3,设备无关位图显示

建立了一个类Dib Dib.h #pragma once #include “afx.h” class CDib :public CObject { public: CDib(); ~CDib(); char* GetFileName(); BOOL IsValid(); DWORD GetSize(); UINT GetWidth(); UINT GetHeight(); UINT GetNumberOfColors(); RGBQUAD* GetRGB(); BYTE* GetDat…

【JavaScript 漫游】【022】事件模型

文章简介 本篇文章为【JavaScript 漫游】专栏的第 022 篇文章&#xff0c;对 JavaScript 中事件模型相关的知识点进行了总结。 监听函数 浏览器的事件模型&#xff0c;就是通过监听函数&#xff08;listener&#xff09;对事件做出反应。事件发生后&#xff0c;浏览器监听到…

【 C++ 】闭散列哈希表的模拟实现

哈希节点状态 我们都很清楚数组里的每一个值无非三种状态&#xff1a; 如果某下标没有值&#xff0c;则代表空EMPTY。如果有值在代表存在EXIST。如果此位置的值被删掉了&#xff0c;则表示为DELETE。 而这三种状态我们可以借助enum枚举来帮助我们表示数组里每个位置的状态。…

Oracle ADG相关介绍

文章目录 一、ADG原理1、ADG介绍2、ADG搭建流程 二、ADG相关参数三、增量修复 一、ADG原理 1、ADG介绍 Oracle ADG&#xff08;Advanced Data Guard&#xff09;是Oracle数据库的一项高可用和灾难恢复技术&#xff0c;它通过将数据保持在物理备库中来提供数据保护和容灾能力。…

每日五道java面试题之spring篇(七)

目录&#xff1a; 第一题. 什么是Spring beans&#xff1f;第二题. 一个 Spring Bean 定义 包含什么&#xff1f;第三题. 如何给Spring 容器提供配置元数据&#xff1f;Spring有几种配置方式?第四题. Spring基于xml注入bean的几种方式?第五题&#xff1a;你怎样定义类的作用域…

POST参数里加号+变成空格的问题处理

今天遇到个这样的问题&#xff0c;从前端传到后端的加密报文&#xff0c;里面包含了号&#xff0c;但在后端日志输出看出&#xff0c;变成空格。这个是由于经过RSA加密后引起的 解决办法&#xff1a; 1.前端转码&#xff1a;使用encodeURIComponent对参数进行转码 2.后端解码…

【自然语言处理四-从矩阵操作角度看 自注意self attention】

自然语言处理四-从矩阵操作角度看 自注意self attention 从矩阵角度看self attention获取Q K V矩阵注意力分数softmax注意力的输出再来分析整体的attention的矩阵操作过程从矩阵操作角度看&#xff0c;self attention如何解决问题的&#xff1f;W^q^ W^k^ W^v^这三个矩阵怎么获…

OpenCV 16 - Qt使用opencv视觉库

1 下载好opencv视觉库 不知道怎么下载和编译opencv视觉库的可以直接使用这个 : opencvcv_3.4.2_qt 2 解压opencv包 3 打开opencv的安装目录 4.打开x86/bin 复制里面所有的dll文件&#xff0c;黏贴到C/windows/syswow64里面 5 新建Qt项目 6 修改pro文件:添加对应的头文件和库文件…

【GameFramework框架内置模块】4、内置模块之调试器(Debugger)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群&#xff1a;398291828 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a;…

matlab批量替换txt文本文件的特定行的内容

1.下图所示&#xff0c;我想要替换第14行。 2.运行代码后&#xff0c;第14行已经更改为需要的内容。 clc,clear; %%----------------------需要更改的地方------------------------------------ % 设置要操作的文本文件路径&#xff0c;替换为你自己的文件路径 path D:\paper_…

Linux Nginx SSL 证书配置正确,扔展示不安全

Nginx SSL 配置 首先我能够确定自己的Nginx SSL是配置正确的&#xff1a; 问题展示 通过浏览器访问自己域名&#xff0c;点击不安全后查看证书&#xff0c;展示的证书并不是自己所配置的证书&#xff0c;如下&#xff1a; 通过curl -vvv https://域名访问返回的证书是过期…

60V耐压降压恒流芯片SL6015B替代PT4115

SL6015B是一款耐压60V的降压恒流芯片&#xff0c;可用于替代PT4115。它具有以下特点&#xff1a; 1. 耐压60V&#xff0c;适用于高电压应用场景&#xff1b; 2. 恒流输出&#xff0c;能够提供稳定的电流输出&#xff1b; 3. 内部集成软启动功能&#xff0c;有效减小启动电流&am…

html5盒子模型

1.边框的常用属性 border-color 属性 说明 示例 border-top-color 上边框颜色 border-top-color:#369; border-right-color 右边框颜色 border-right-color:#369; border-bottom-color 下边框颜色 border-bottom-color:#fae45b; border-left-color 左边框颜色…

BUUCTF crypto做题记录(9)新手向

一、rsa2 得到题目代码如下&#xff1a; N 101991809777553253470276751399264740131157682329252673501792154507006158434432009141995367241962525705950046253400188884658262496534706438791515071885860897552736656899566915731297225817250639873643376310103992170…

探究前端路由hash和history的实现原理(包教包会)

今天我们来讲一讲前端中很重要的一个部分路由&#xff08;router&#xff09;&#xff0c;想必前端小伙伴对‘路由’一词都不会感到陌生。但是如果哪天面试官问你&#xff0c;能大概说一说前端路由的实现原理吗&#xff1f; 你又会如何应对呢&#xff1f; 今天勇宝就带着大家一…

41.仿简道云公式函数实战-数学函数-SUMIF

1. SUMIF函数 SUMIF 函数可用于计算子表单中满足某一条件的数字相加并返回和。 2. 函数用法 SUMIF(range, criteria, [sum_range]) 其中各参数的含义及使用方法如下&#xff1a; range&#xff1a;必需&#xff1b;根据 criteria 的条件规则进行检测的判断字段。支持的字段…

RC4算法

RC4 RC4是Ron Rivest为RSA设计的序列密码,RC4算法简单、速度快、容易用软硬件实现,因此应用广泛。比如WEP、WPA、SSL/TLS应用了RC4;Windows、Lotus notes、Apple APCE等软件系统也应用了RC4。 1. RC4算法 RC4具体算法如下: 第一步:密钥调度算法(The Key-Scheduling Alg…

3. Java中的锁

文章目录 乐观锁与悲观锁乐观锁(无锁编程,版本号机制)悲观锁两种锁的伪代码比较 通过 8 种锁运行案例,了解锁锁相关的 8 种案例演示场景一场景二场景三场景四场景五场景六场景七场景八 synchronized 有三种应用方式8 种锁的案例实际体现在 3 个地方 从字节码角度分析 synchroni…

排序(9.17)

1.排序的概念及其运用 1.1排序的概念 排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性 &#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记…

OSI参考模型和TCP/IP网络参考模型

1、OSI参考模型 1.1 产生背景 为了解决网络之间的兼容性问题,实现网络设备间的相互通讯,国际标准化组织ISO于1984年提出了OSIRM(Open System Interconnection Reference Model,开放系统互连参考模型)。OSI参考模型很快成为计算机网络通信的基础模型。由于种种原因,并没有…