当前位置: 首页 > news >正文

使用Go语言生成Excel任务表依赖图(Markdown文件mermaid图)

一、前言

在游戏中,任务是非常常见的玩法,可能会有主线任务,支线任务以及其它一些类型的任务,各任务可能还会有前置任务,即需要完成某个任务之后,才能做当前任务。在游戏开发中,配置表可以使用Excel来编辑,如果是任务表,可能会是如下配置方式:

TaskIDTaskTitlePreTask
10任务100
20任务200
11任务1110
21任务2120

当任务比较多的时候,它们的依赖关系将变得不直观,很容易出错,出错也不容易发现。

有没比较直观的方式进行查看,排错呢?笔者想到了目前非常流程的Markdown文件,它可以简单地通过文本的方式输入然后输出强大的各种图表。这里就可以使用mermaid图来直观展现。

关于mermaid图可以去官网https://mermaid.js.org/intro/查看用例。

下图为生成后的效果图:

0:无效任务ID
21:任务21
20:任务20
11:任务11
10:任务10

注意:mermaid图在渲染时,如果不设置subgraph则可能会出现乱序问题,即不是按代码中出现的顺序渲染。

二、实现

为了方便Go读取Excel,需要使用相关的Excel库,笔者使用excelize库。

根据前面的效果图,可以知道,这其实就是一个深度优先的树,实现方式有两种,一种是使用递归的方式来实现,这种方式实现起来简单,但是如果层次很深,那可能会出现栈溢出;另一种方式就是使用栈的方式来实现,将每一层节点先压栈,然后从栈顶取出一个节点然后再将其所有子节点入栈,再从栈顶取出一个节点处理,依此类推,直到栈中所有节点处理完毕。

下面列出使用递归方式实现的版本:

/*
MIT License# Copyright (c) 2023 WittonBellPermission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package mainimport ("flag""fmt""os""path/filepath""strings""github.com/xuri/excelize/v2"
)var taskIDField string
var taskTitleField string
var preTaskField string
var noCaseSensitive bool // 是否不区分大小写
var fieldNameRow uint    // 字段名所在行号
var dataStartRow uint    // 数据开始行号type node struct {taskID    stringtaskTitle string
}type multiMap map[string][]*nodefunc (slf multiMap) Add(key string, nd *node) {if len(slf) == 0 {slf[key] = []*node{nd}} else {slf[key] = append(slf[key], nd)}
}func (slf multiMap) Get(key string) []*node {if slf == nil {return nil}return slf[key]
}func (slf multiMap) Del(key string) {delete(slf, key)
}func searchKeyCol(rows *excelize.Rows) (TaskIDCol, PreTaskIDCol, TitleCol int) {row, err := rows.Columns()if err != nil {fmt.Println(err.Error())}for i, col := range row {name := colif noCaseSensitive {name = strings.ToLower(col)}if name == preTaskField {PreTaskIDCol = i + 1} else if name == taskIDField {TaskIDCol = i + 1} else if name == taskTitleField {TitleCol = i + 1}}return
}func readExcel(filePath string) multiMap {fd, err := excelize.OpenFile(filePath)if err != nil {fmt.Printf("读取文件`%s`失败", filePath)return nil}defer func() {fd.Close()}()TaskIDCol, PreTaskIDCol, TitleCol := -1, -1, -1sheetName := fd.GetSheetName(0)rows, err := fd.Rows(sheetName)if err != nil {return nil}defer func() {rows.Close()}()m := multiMap{}for i := 1; rows.Next(); i++ {if i == int(fieldNameRow) {TaskIDCol, PreTaskIDCol, TitleCol = searchKeyCol(rows)isOk := trueif TaskIDCol < 0 {isOk = falsefmt.Printf("要求字段名:%s\n", taskIDField)}if PreTaskIDCol < 0 {isOk = falsefmt.Printf("要求字段名:%s\n", preTaskField)}if TitleCol < 0 {isOk = falsefmt.Printf("要求字段名:%s\n", taskTitleField)}if !isOk {return nil}}if i < int(dataStartRow) {continue}TaskIDCell, err := excelize.CoordinatesToCellName(TaskIDCol, i)if err != nil {continue}PreTaskIDCell, err := excelize.CoordinatesToCellName(PreTaskIDCol, i)if err != nil {continue}TitleColCell, err := excelize.CoordinatesToCellName(TitleCol, i)if err != nil {continue}TaskID, err := fd.GetCellValue(sheetName, TaskIDCell)if err != nil || TaskID == "" {continue}Title, err := fd.GetCellValue(sheetName, TitleColCell)if err != nil || Title == "" {continue}PreTaskID, err := fd.GetCellValue(sheetName, PreTaskIDCell)if err != nil {continue}if PreTaskID == "" {PreTaskID = "0"}m.Add(PreTaskID, &node{taskID: TaskID, taskTitle: Title})}return m
}func usage() {w := flag.CommandLine.Output()fmt.Fprintf(w, "%s 应用程序是将Excel任务表中的关系转换成Markdown的mermaid图,方便使用Markdown工具直观地查看任务依赖。", filepath.Base(os.Args[0]))fmt.Fprintln(w)fmt.Fprintf(w, "命令格式:%s -hr [字段所在行号] -dr [数据起始行号] [-nc] -id [任务ID字段名] -t [任务标题字段名] -pid [前置任务ID字段名] -o <输出文件> <Excel文件路径>", filepath.Base(os.Args[0]))fmt.Fprintln(w)flag.CommandLine.PrintDefaults()fmt.Fprintln(w, "  -h")fmt.Fprintln(w, "    \t显示此帮助")
}func main() {var outputFile stringflag.CommandLine.Usage = usageflag.BoolVar(&noCaseSensitive, "nc", false, "字段名不区分大小写")flag.UintVar(&fieldNameRow, "hr", 1, "字段所在行号")flag.UintVar(&dataStartRow, "dr", 2, "数据起始行号")flag.StringVar(&taskIDField, "id", "ID", "-id [任务ID字段名]")flag.StringVar(&taskTitleField, "t", "Title", "-t [任务标题字段名]")flag.StringVar(&preTaskField, "pid", "PreTask", "-pid [前置任务ID字段名]")flag.StringVar(&outputFile, "o", "任务图.md", "-o <输出文件>")flag.Parse()if flag.NArg() < 1 {usage()return}if noCaseSensitive {taskIDField = strings.ToLower(taskIDField)taskTitleField = strings.ToLower(taskTitleField)preTaskField = strings.ToLower(preTaskField)}mapTask := readExcel(flag.Arg(0))buildGraph(mapTask, outputFile)
}func buildGraph(mapTask multiMap, outputFile string) {graph := "```mermaid\ngraph TB\n"graph += "subgraph  \n"root := mapTask.Get("0")for _, v := range root {graph += visit(rootNodeName, v, mapTask)}graph += "end\n"graph += "```"os.WriteFile(outputFile, []byte(graph), os.ModePerm)fmt.Println("完成")
}func visit(parent string, nd *node, mapTask multiMap) string {slice := mapTask.Get(nd.taskID)graph := fmt.Sprintf("%s --> %s:%s\n", parent, nd.taskID, nd.taskTitle)if parent == rootNodeName {graph += "subgraph  \n"}for _, x := range slice {graph += visit(fmt.Sprintf("%s:%s", nd.taskID, nd.taskTitle), x, mapTask)}mapTask.Del(nd.taskID)if parent == rootNodeName {graph += "end\n"}return graph
}

使用栈实现的版本笔者放在excelTask2md了。

相关文章:

使用Go语言生成Excel任务表依赖图(Markdown文件mermaid图)

一、前言 在游戏中&#xff0c;任务是非常常见的玩法&#xff0c;可能会有主线任务&#xff0c;支线任务以及其它一些类型的任务&#xff0c;各任务可能还会有前置任务&#xff0c;即需要完成某个任务之后&#xff0c;才能做当前任务。在游戏开发中&#xff0c;配置表可以使用…...

C语言和C++的区别在哪?如何自学C++?

C语言和C是两种不同的编程语言&#xff0c;它们在语法、特性和用途上有一些区别。以下是C语言和C的一些主要区别&#xff1a; 面向对象编程&#xff1a;C是一种支持面向对象编程的语言&#xff0c;它在C语言的基础上添加了类、对象、继承、多态等面向对象的特性。而C语言是一种…...

功能强大的开源数据中台系统 DataCap 1.13.0 发布

推荐一套基于 SpringBoot 开发的简单、易用的开源权限管理平台&#xff0c;建议下载使用: https://github.com/devlive-community/authx 推荐一套为 Java 开发人员提供方便易用的 SDK 来与 OpenAI 的 API 进行交互组件&#xff1a;https://github.com/devlive-community/openai…...

JTS Self-intersection异常TopologyException: side location conflict解决办法

JTS Self-intersection异常TopologyException: side location conflict解决办法 举例&#xff1a;问题围栏 MULTIPOLYGON (((114.0905685 32.1120567, 114.0905685 32.112957, 114.0905685 32.1138535, 114.0905685 32.1147537, 114.0905685 32.115654, 114.0905685 32.11655…...

Maven: No compiler is provided in this environment.

在Eclipse中运行Maven项目&#xff0c;报错&#xff1a; No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK? 解决方法&#xff1a; Windows > Preferences > Java > Installed JREs > Add > Standard VM,…...

.NET-10. 其他-VSTO+VBA

VSTOVBA 前言VSTO 外接程序介绍&#xff1a;VSTO参考链接&#xff1a;VSTO 例子&#xff1a; VBA:参考链接&#xff1a; 前言 主要用于Excel插件。 VSTO 外接程序介绍&#xff1a; Excel、Word、PowerPoint、Project、Visio等等Office应用程序 相对简单 VSTO参考链接&#x…...

相机传感器格式与镜头光圈参数

相机靶面大小 CCD/CMOS图像传感器尺寸&#xff08;sensor format&#xff09;1/2’‘、1/3’‘、1/4’实际是多大 1英寸——靶面尺寸为宽12.7mm*高9.6mm&#xff0c;对角线16mm。 2/3英寸——靶面尺寸为宽8.8mm*高6.6mm&#xff0c;对角线11mm。 1/2英寸——靶面尺寸为宽6.…...

Android 设置头像(拍照获取、相册获取、裁剪照片)

在Android原生态开发过程中&#xff0c;往往会设计到用户头像的设置问题&#xff0c;一般来讲设置头像需要用到拍照、获取照片、存储照片、裁剪照片、显示照片等问题&#xff0c;本文将一步一步的进行说明讲解。 首先需要强调几点我在开发过程中遇到的问题。 权限问题&#xf…...

android开发之Android 自定义滑动解锁View

自定义滑动解锁View 需求如下&#xff1a; 近期需要做一个类似屏幕滑动解锁的功能&#xff0c;右划开始&#xff0c;左划暂停。 需求效果图如下 实现效果展示 自定义view如下 /** Desc 自定义滑动解锁View Author ZY Mail sunnyfor98gmail.com Date 2021/5/17 11:52 *…...

CAD绘制法兰、添加光源、材质并渲染

首先绘制两个圆柱体&#xff0c;相互嵌套 在顶部继续绘制圆柱体&#xff0c;这是之后要挖掉的部分 在中央位置绘制正方形 用圆角工具&#xff1a; 将矩形的四个角分别处理&#xff0c;效果&#xff1a; 用拉伸工具 向上拉伸到和之前绘制的圆柱体高度齐平 绘制一个圆柱体&#…...

ChatGPT访问流量下降的原因分析

​自从OpenAI的ChatGPT于11月问世以来&#xff0c;这款聪明的人工智能聊天机器人就席卷了全世界&#xff0c;人们在试用该工具的同时也好奇该技术到底将如何改变我们的工作和生活。 但近期Similarweb表示&#xff0c;自去ChatGPT上线以来&#xff0c;该网站的访问量首次出现下…...

干货 | 详述 Elasticsearch 向量检索发展史

1. 引言 向量检索已经成为现代搜索和推荐系统的核心组件。 通过将复杂的对象&#xff08;例如文本、图像或声音&#xff09;转换为数值向量&#xff0c;并在多维空间中进行相似性搜索&#xff0c;它能够实现高效的查询匹配和推荐。 图片来自&#xff1a;向量数据库技术鉴赏【上…...

mysql常见面试题,高频题目放送

互联网的产品架构是包含这接入层&#xff0c;逻辑处理以及储存层的&#xff0c;其中储存层承载着较多的数据以及持久化的任务&#xff0c;而说到储存层&#xff0c;避免不了说到数据库&#xff0c;在我们面试的时候&#xff0c;数据库的知识题目占比是非常多的&#xff1a; 1.…...

使用 PowerShell 将 Excel 中的每个工作表单独另存为独立的文件

导语&#xff1a;在日常工作中&#xff0c;我们经常需要处理 Excel 文件。本文介绍了如何使用 PowerShell 脚本将一个 Excel 文件中的每个工作表单独另存为独立的 Excel 文件&#xff0c;以提高工作效率。 1. 准备工作 在开始之前&#xff0c;请确保已经安装了 Microsoft Exc…...

python提取pdf图片

import fitz import re import osdef save_pdf_img(path, save_path):path: pdf的路径save_path : 图片存储的路径# 使用正则表达式来查找图片checkXO r"/Type(? */XObject)"checkIM r"/Subtype(? */Image)"# 打开pdfdoc fitz.open(path)# 图片计数im…...

Vue3 表单输入绑定简单应用

去官网学习→表单输入绑定 | Vue.js 运行示例&#xff1a; 代码&#xff1a;HelloWorld.vue <template><div class"hello"><h1>Vue 表单输入绑定</h1><input type"text" placeholder"输入框" v-model"msg"…...

如何解决 Elasticsearch 查询缓慢的问题以获得更好的用户体验

作者&#xff1a;Philipp Kahr Elasticsearch Service 用户的重要注意事项&#xff1a;目前&#xff0c;本文中描述的 Kibana 设置更改仅限于 Cloud 控制台&#xff0c;如果没有我们支持团队的手动干预&#xff0c;则无法进行配置。 我们的工程团队正在努力消除对这些设置的限制…...

近期学习练习

练习&#xff1a; 或&#xff1a; //sqrt是数学库函数&#xff0c;开平方 //头文件为math.h 或...

平台安全之中间件安全

理解中间件 一次web访问的顺序&#xff0c;web浏览器->web服务器&#xff08;狭义&#xff09;->web容器->应用服务器->数据库服务器 web服务器 广义&#xff1a;提供广义web服务的软件或主机 狭义&#xff1a;提供w3服务的软件或主机&#xff0c;即Web服务器软件…...

芒果 TV 基于 Flink 的实时数仓建设实践

公司简介&#xff1a;芒果 TV 作为湖南广电旗下互联网视频平台&#xff0c;在“一云多屏&#xff0c;多元一体”的战略指导下&#xff0c;通过内容自制&#xff0c;培植核心竞争力&#xff0c;从独播、独特走向独创&#xff0c;并通过市场化运作完成 A 轮、B 轮融资&#xff0c…...

2026年AIGC降重网站推荐,免费论文查重/Writepass/万方查重/AIGC降重,AIGC降重网站哪个好

在当今学术和创作领域&#xff0c;原创性和效率是至关重要的因素。随着AIGC&#xff08;人工智能生成内容&#xff09;技术的快速发展&#xff0c;抄袭和重复内容的问题日益凸显。AIGC降重技术应运而生&#xff0c;它利用先进的算法和大数据资源&#xff0c;能够精准识别文本中…...

聊一聊 C# 中的闭包陷阱:foreach 循环的坑你还记得吗?诖

. GIF文件结构 相比于 WAV 文件的简单粗暴&#xff0c;GIF 的结构要精密得多&#xff0c;因为它天生是为了网络传输而设计的&#xff08;包含了压缩机制&#xff09;。 当我们用二进制视角观察 GIF 时&#xff0c;它是由一个个 数据块&#xff08;Block&#xff09; 组成的&…...

一台电脑如何实现四人同屏游戏?Nucleus Co-Op 分屏神器深度解析

一台电脑如何实现四人同屏游戏&#xff1f;Nucleus Co-Op 分屏神器深度解析 【免费下载链接】nucleuscoop Starts multiple instances of a game for split-screen multiplayer gaming! 项目地址: https://gitcode.com/gh_mirrors/nu/nucleuscoop 你是否曾经梦想过和朋友…...

OpenEMR一体化医疗管理解决方案:实现高效合规的电子病历系统

OpenEMR一体化医疗管理解决方案&#xff1a;实现高效合规的电子病历系统 【免费下载链接】openemr The most popular open source electronic health records and medical practice management solution. 项目地址: https://gitcode.com/GitHub_Trending/op/openemr 在医…...

前端团队协作:别让沟通成本拖垮你的项目

前端团队协作&#xff1a;别让沟通成本拖垮你的项目 什么是前端团队协作&#xff1f; 前端团队协作是指前端开发团队成员之间的协调与配合&#xff0c;包括代码管理、任务分配、沟通交流等方面。别以为前端开发只是写代码&#xff0c;团队协作不好&#xff0c;项目就会变成一场…...

避坑指南:Ascend 310芯片+CANN工具包在麒麟系统下的5个常见安装错误

Ascend 310芯片CANN工具包在麒麟系统下的5个典型安装陷阱与解决方案 当Ascend 310芯片遇上麒麟操作系统&#xff0c;这种国产硬件的黄金组合本应带来无缝的开发体验&#xff0c;但实际部署中总有几个"暗礁"让开发者措手不及。不同于常规安装教程&#xff0c;本文将直…...

CMOS功耗优化实战:静态与动态功耗的深度解析与设计策略

1. CMOS功耗优化的核心挑战 做低功耗芯片设计的朋友们应该都深有体会&#xff0c;CMOS器件的功耗就像个无底洞&#xff0c;稍不注意就会把电池电量吞噬殆尽。我十年前刚入行时&#xff0c;就曾经因为忽视功耗优化&#xff0c;设计出的芯片续航时间直接腰斩。经过这些年的摸爬滚…...

Pixel Couplet Gen惊艳案例:神荼郁垒像素方块+物理卷轴动态渲染

Pixel Couplet Gen惊艳案例&#xff1a;神荼郁垒像素方块物理卷轴动态渲染 1. 项目概览 当AI技术遇上复古游戏美学&#xff0c;Pixel Couplet Gen为我们带来了一场视觉与文化的盛宴。这款基于ModelScope大模型的春联生成器&#xff0c;彻底颠覆了传统春联的设计语言&#xff…...

Qwen3-4B模型在STM32嵌入式开发中的应用:代码注释生成与调试日志分析

Qwen3-4B模型在STM32嵌入式开发中的应用&#xff1a;代码注释生成与调试日志分析 如果你是一位STM32开发者&#xff0c;下面这个场景你一定不陌生&#xff1a;面对一段几个月前自己写的、涉及复杂定时器配置或CAN总线通信的代码&#xff0c;你皱着眉头看了半天&#xff0c;愣是…...

EasyEdit完全教程:从事实编辑到概念编辑的完整路径

EasyEdit完全教程&#xff1a;从事实编辑到概念编辑的完整路径 【免费下载链接】EasyEdit [ACL 2024] An Easy-to-use Knowledge Editing Framework for LLMs. 项目地址: https://gitcode.com/gh_mirrors/ea/EasyEdit EasyEdit是一个功能强大的大语言模型知识编辑框架&a…...