使用 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;
					}
				}
				else
					finished = 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));
			else
				vec_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;
					}
				}
				else
					finished = 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 2
        0 1 3
        0 2 1
        0 2 3
        0 3 1
        0 3 2
        1 0 2
        1 0 3
        1 2 0
        1 2 3
group contains 10 results:

        1 3 0
        1 3 2
        2 0 1
        2 0 3
        2 1 0
        2 1 3
        2 3 0
        2 3 1
        3 0 1
        3 0 2
group contains 4 results:

        3 1 0
        3 1 2
        3 2 0
        3 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;
					}
				}
				else
					finished = true;
			} while (finished == false && hit == false);
		}
	}while(finished == false);
};

4. 体会与思考

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

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

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

运行效果:

co_yield

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】head命令使用

head命令 head是一个在 Unix 和 Unix-like 操作系统中常用的命令行工具&#xff0c;用于输出文件的前 n 行。默认为 10&#xff0c;即显示 10 行的内容。 语法 head [options] [file(s)] head命令 -Linux手册页 选项及作用 执行令 &#xff1a; head --help 执行命令结果…

UGUISuperScrollView

UGUISuperScrollView v2.5.3 基于UGUI ScrollRect,UGUI超级滚动视图提供了易于自定义的滚动视图。它是一组C#脚本,可以帮助您创建所需的ScrollView。它功能强大,性能经过高度优化。 - 列表视图演示: 列表视图从上到下演示 列表视图从左到右演示 列表视图 选择 删除 演…

LabVIEW光偏振态转换及检测仿真系统

LabVIEW光偏振态转换及检测仿真系统 随着光学技术的发展&#xff0c;光偏振态的研究与应用越来越广泛。为了深入理解光的偏振现象&#xff0c;开发了一套基于LabVIEW的光偏振态转换及检测仿真系统。该系统不仅能够模拟线偏振光、圆偏振光、椭圆偏振光等不同偏振态的产生与转换…

【pytorch】函数记录

你好你好&#xff01; 以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 torch.sum()torch.argmax()torch.nn.Parametertorch.unbindtorch.optim.Adam()[^adam]torch.cattorch.unsqueeze()torch.normalize()[^l2]torch.eyetorch.mmto…

golang学习7,glang的web的restful接口结构体传参

接口&#xff1a; //POST请求 返回json 接口传参json r.POST("/postJson", controller.PostUserInfo) 1.定义结构体 //定义结构体 type Search struct {Id intName string }2.结构体传参 //结构体传参 func PostUserInfo(c *gin.Context) {search : &Searc…

SD-WAN专线加速效果如何?企业如何选择SD-WAN加速专线方案?

在数字化时代&#xff0c;企业的网络需求日益增长&#xff0c;对于网络性能和安全性的要求也越来越高。SD-WAN专线加速技术应运而生&#xff0c;成为企业提升网络效率和保障数据安全的重要工具。本文将探讨SD-WAN专线加速的效果&#xff0c;以及企业在选择SD-WAN加速专线方案时…

golang使用gorm操作mysql2

1.接口 //POST请求 db操作&#xff0c;新增数据r.POST("/add", controller.SaveUser) 2.接收前端传的参数并解析成结构化数据&#xff08;类似java的 json转为实体类属性&#xff09; package controllerimport ("gin/dao""github.com/gin-gonic/g…

node 之 http模块

1.什么是http模块 在网络节点中&#xff0c;负责消费资源的电脑叫做客户端&#xff1b;负责对外提供网络资源的电脑&#xff0c;叫做服务器 http模块是node.js官方提供的&#xff0c;用来创建web服务器的模块&#xff0c;通过http模块提供的http.createServer()方法&#xff0c…

java使用e签宝实现合同文件签名盖章

过程细节比较多&#xff0c;官网写得也不太明白&#xff0c;上面只是写了大概步骤&#xff0c;不懂的可以私聊 注意&#xff1a;测试沙箱环境时合同模板&#xff0c;模板上设置的填写表单&#xff08;也就是控件&#xff09;只能用官方提供的接口创建 切换正式后&#xff0c;可…

【JavaScript】面试手撕防抖

引入 防抖可是前端面试时最频繁考察的知识点了&#xff0c;首先&#xff0c;我们先了解防抖的概念是什么。咳咳。&#x1f440; 防抖&#xff1a; 首先它是常见的性能优化技术&#xff0c;主要用于处理频繁触发的浏览器事件&#xff0c;如窗口大小变化、滚动事件、输入框内容…

神经网络结构搜索(NAS)

华为诺亚AI系统工程实验室主任刘文志解读如何使用AutoML预测基站流量 - 知乎讲师介绍&#xff1a;刘文志&#xff08;花名风辰&#xff09;&#xff0c;华为诺亚AI系统工程实验室主任&#xff0c;异构并行计算专家&#xff0c;毕业于中国科学院研究生院&#xff0c;闻名于并行计…

2024Java大厂面试集合,java面试题库及答案

前言 Spring 也算有多年的历史了&#xff0c;已成为Java应用程序开发框架的事实标准。在如此悠久的历史背景下&#xff0c;有人可能会认为Spring放慢了脚步&#xff0c;躺在了自己的荣誉簿上&#xff0c;再也做不出什么新鲜的东西&#xff0c;或者是让人激动的东西。甚至有人说…

Django配置静态文件

Django配置静态文件 目录 Django配置静态文件静态文件配置调用方法 一般我们将html文件都放在默认templates目录下 静态文件放在static目录下 static目录大致分为 js文件夹css文件夹img文件夹plugins文件夹 在浏览器输入url能够看到对应的静态资源&#xff0c;如果看不到说明…

【Maven】Maven 基础教程(一):基础介绍、开发环境配置

Maven 基础教程&#xff08;一&#xff09;&#xff1a;基础介绍、开发环境配置 1.Maven 是什么1.1 构建1.2 依赖 2.Maven 开发环境配置2.1 下载安装2.2 指定本地仓库2.3 配置阿里云提供的镜像仓库2.4 配置基础 JDK 版本2.5 配置环境变量 1.Maven 是什么 Maven 是 Apache 软件…

网安入门18-XSS(靶场实战)

HTML实体化编码 为了避免 XSS 攻击&#xff0c;会将<>编码为<与>&#xff0c;这些就是 HTML 实体编码。 编码前编码后不可分的空格 < (小于符号)< > (大于符号)> & (与符号)&amp;″ (双引号)&quot;’ (单引号)&apos;© (版权符…

栈和队列——c语言实现栈

本节复习栈和队列中栈的增删查改。 首先回顾一下栈的性质&#xff1a; 栈的存储数据的原则是“后入先出”&#xff0c; 先入的在栈底&#xff0c; 后入的在栈顶。 弹出数据时在栈顶弹出。 开始实现栈的接口 栈的所有函数接口 //栈的初始化 void StackInit(Stack* pst); //入栈…

Monkey测试必现ANR问题分析与解决

AAA项目Monkey测试必现ANR问题分析 【摘要】ANR(Application Not responding)&#xff0c;是指应用程序未响应&#xff0c;Android系统对于一些事件需要在一定的时间范围内完成&#xff0c;如果超过预定时间能未能得到有效响应或者响应时间过长&#xff0c;比如输入事件5s内未…

大模型日报|今日必读的7篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.Sora综述&#xff1a;大型视觉模型的背景、技术、局限和机遇 Sora 是 OpenAI 于 2024 年 2 月发布的文生视频人工智能&#xff08;AI&#xff09;模型。经过训练&#xff0c;Sora 能根据文字说明生成逼真或富有想象…

2.27数据结构

1.链队 //link_que.c #include "link_que.h"//创建链队 Q_p create_que() {Q_p q (Q_p)malloc(sizeof(Q));if(qNULL){printf("空间申请失败\n");return NULL;}node_p L(node_p)malloc(sizeof(node));if(LNULL){printf("申请空间失败\n");return…

一周学会Django5 Python Web开发-Django5列表视图ListView

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计27条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…