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

news2025/1/16 2:37:50

在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.coloradmin.cn/o/1470689.html

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

相关文章

【数据结构】图——最短路径

最短路径问题&#xff1a;从在带权有向图G中的某一顶点出发&#xff0c;找出一条通往另一顶点的最短路径&#xff0c;最短也就是沿路径各边的权值总和达到最小。 最短路径分为图中单源路径和多源路径。 本文会介绍Dijkstra和Bellman-Ford解决单源路径的问题 Floyd-Warshall解…

SCI一区 | Matlab实现ST-CNN-MATT基于S变换时频图和卷积网络融合多头自注意力机制的多特征分类预测

SCI一区 | Matlab实现ST-CNN-MATT基于S变换时频图和卷积网络融合多头自注意力机制的故障多特征分类预测 目录 SCI一区 | Matlab实现ST-CNN-MATT基于S变换时频图和卷积网络融合多头自注意力机制的故障多特征分类预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍…

【牛客】2024牛客寒假算法基础集训营6ABCDEGHIJ

文章目录 A 宇宙的终结题目大意主要思路代码 B 爱恨的纠葛题目大意主要思路代码 C 心绪的解剖题目大意主要思路代码 D 友谊的套路题目大意主要思路代码 E 未来的预言题目大意主要思路代码 G 人生的起落题目大意主要思路代码 I 时空的交织题目大意主要思路代码 J 绝妙的平衡题目…

ChatGPT调教指南 | 咒语指南 | Prompts提示词教程(三)

在人工智能成为我们日常互动中无处不在的一部分的时代&#xff0c;与大型语言模型(llm)有效沟通的能力是无价的。“良好提示的26条原则”为优化与这些复杂系统的交互提供了全面的指导。本指南证明了人类和人工智能之间的微妙关系&#xff0c;强调清晰、专一和结构化的沟通方法。…

leetcode hot100 买卖股票最佳时机3

本题中&#xff0c;依旧可以采用动态规划来进行解决&#xff0c;之前的两个题我们都是用二维数组dp[i][2]来表示的&#xff0c;其中i表示第i天&#xff0c;2表示长度为2&#xff0c;其中0表示不持有&#xff0c;1表示持有。 本题中&#xff0c;说至多完成两笔交易&#xff0c;也…

RabbitMQ 面试八股题整理

前言&#xff1a;本文是博主网络自行收集的一些RabbitMQ相关八股文&#xff0c;还在准备暑期实习&#xff0c;后续应该会持续更新...... 参考&#xff1a;三天吃透RabbitMQ面试八股文_牛客网 目录 RabbitMQ概述 什么是 RabbitMQ&#xff1f; 说一说RabbitMQ中的AMQP 为什么…

单机取证-信息安全管理与评估-2022年国赛真题-环境+wp

🍬 博主介绍 博主介绍:大家好,我是 Mikey ,很高兴认识大家~ 主攻:【应急响应】 【python】 【数字取证】【单机取证】【流量分析】【MISC】 🎉点赞➕评论➕收藏 == 养成习惯(一键三连)😋 🎉欢迎关注💗一起学习👍一起讨论⭐️一起进步 作者水平有限,欢迎各…

网络层的DDoS攻击与应用层的DDoS攻击之间的区别

DDoS攻击&#xff08;即“分布是拒绝服务攻击”&#xff09;&#xff0c;是基于DoS的特殊形式的拒绝服务攻击&#xff0c;是一种分布式、协作的大规模攻击方式&#xff0c;主要瞄准一些企业或政府部门的网站发起攻击。根据攻击原理和方式的区别&#xff0c;可以把DDoS攻击分为两…

(done) 如何判断一个矩阵是否可逆?

参考视频&#xff1a;https://www.bilibili.com/video/BV15H4y1y737/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 这个视频里还暗含了一些引理 1.若 AX XB 且 X 和 A,B 同阶可逆&#xff0c;那么 A 和 B 相似。原因&#xff1…

RDMA内核态函数ib_post_recv()源码分析

接上文&#xff0c;上文分析了内核rdma向发送队列添加发送请求的函数ib_post_send&#xff0c;本文分析一下向接收队列添加接收请求的函数ib_post_recv。其实函数调用流程与上文类似&#xff0c;不再重复说明&#xff0c;可参考链接。 函数调用过程 最终会调用到这个函数 下面…

Stable Diffusion 绘画入门教程(webui)-ControlNet(Inpaint)

上篇文章介绍了语义分割Tile/Blur&#xff0c;这篇文章介绍下Inpaint&#xff08;重绘&#xff09; Inpaint类似于图生图的局部重绘&#xff0c;但是Inpain效果要更好一点&#xff0c;和原图融合会更加融洽&#xff0c;下面是案例&#xff0c;可以看下效果&#xff08;左侧原图…

【Java多线程】对线程池的理解并模拟实现线程池

目录 1、池 1.1、线程池 2、ThreadPoolExecutor 线程池类 3、Executors 工厂类 4、模拟实现线程池 1、池 “池”这个概念见到非常多&#xff0c;例如常量池、数据库连接池、线程池、进程池、内存池。 所谓“池”的概念就是&#xff1a;&#xff08;提高效率&#xff09; 1…

pytorch -- ToTensor使用

1. ToTensor定义 导入&#xff1a;from torchvision import transforms 通过transforms.ToTensor解决两个问题&#xff08;PIL image/numpy.ndarray 转化为 tensor &#xff09; ToTensor()返回一个ToTensor的对象(创建具体的工具)&#xff0c;传入pic就会返回一个Tensor类型的…

应急响应实战笔记03权限维持篇(4)

第4篇&#xff1a;Linux权限维持--后门篇 本文将对Linux下常见的权限维持技术进行解析&#xff0c;知己知彼百战不殆。 1、一句话添加用户和密码 添加普通用户&#xff1a; # 创建一个用户名guest&#xff0c;密码123456的普通用户 useradd -p openssl passwd -1 -salt sal…

Spring的另一大的特征:AOP

目录 AOP &#xff08;Aspect Oriented Programming&#xff09;AOP 入门案例&#xff08;注解版&#xff09;AOP 工作流程——代理AOP切入点表达式AOP 通知类型AOP通知获取数据获取切入点方法的参数获取切入点方法返回值获取切入点方法运行异常信息 百度网盘分享链接输入密码数…

[数据集][目标检测]游泳者溺水数据集VOC+YOLO格式2类别895张

数据集制作单位&#xff1a;未来自主研究中心(FIRC) 数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;895 标注数量(xml文件个数)&#xff1a…

第7.1章:StarRocks性能调优——查询分析

目录 一、查看查询计划 1.1 概述 1.2 查询计划树 1.3 查看查询计划的命令 1.3 查看查询计划 二、查看查询Profile 2.1 启用 Query Profile 2.2 获取 Query Profile 2.3 Query Profile结构与详细指标 2.3.1 Query Profile的结构 2.3.2 Query Profile的合并策略 2.…

计算机视觉基础知识(十五)--卷积神经网络

卷积神经网络简介 CNN--卷积神经网络&#xff0c;是一种前馈神经网络&#xff1b;不同于传统的只有线性连接的神经网络&#xff1b;CNN具有卷积&#xff08;convolution&#xff09;操作、池化&#xff08;pooling&#xff09;和非线性激活函数映射等&#xff1b;经典CNN网络有…

【Linux】--- 详解Linux软件包管理器yum和编辑器vim

目录 一、Linux软件包管理器 - yum1.1 yum和软件包是什么1.2 Linux系统(Centos)的生态1.3 yum相关操作1.4 yum本地配置 二、Linux编辑器 - vim使用2.1 vim的基本概念2.2 vim命令模式命令集2.3 vim末行模式命令集2.4 关于vim的几个相关问题 一、Linux软件包管理器 - yum 1.1 yu…

Codeforce Monsters Attack!(B题 前缀和)

题目描述&#xff1a; 思路&#xff1a; 本人第一次的想法是先杀血量低的第二次想法是先搞坐标近的第三次想法看到数据量这么大&#xff0c; 我先加个和看看貌似我先打谁都行&#xff0c;由此综合一下&#xff0c; 我们可以把每一个不同的坐标当作一轮从最小的坐标开始&#x…