最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Go語言生成Excel任務(wù)表依賴圖的代碼實現(xiàn)

 更新時間:2025年09月09日 10:51:26   作者:witton  
在游戲中,任務(wù)是非常常見的玩法,可能會有主線任務(wù),支線任務(wù)以及其它一些類型的任務(wù),當任務(wù)比較多的時候,它們的依賴關(guān)系將變得不直觀,很容易出錯,所以本文介紹了使用Go語言生成Excel任務(wù)表依賴圖的代碼實現(xiàn),需要的朋友可以參考下

一、前言

在游戲中,任務(wù)是非常常見的玩法,可能會有主線任務(wù),支線任務(wù)以及其它一些類型的任務(wù),各任務(wù)可能還會有前置任務(wù),即需要完成某個任務(wù)之后,才能做當前任務(wù)。在游戲開發(fā)中,配置表可以使用Excel來編輯,如果是任務(wù)表,可能會是如下配置方式:

TaskIDTaskTitlePreTask
10任務(wù)100
20任務(wù)200
11任務(wù)1110
21任務(wù)2120

當任務(wù)比較多的時候,它們的依賴關(guān)系將變得不直觀,很容易出錯,出錯也不容易發(fā)現(xiàn)。

有沒比較直觀的方式進行查看,排錯呢?筆者想到了目前非常流程的Markdown文件,它可以簡單地通過文本的方式輸入然后輸出強大的各種圖表。這里就可以使用mermaid圖來直觀展現(xiàn)。

關(guān)于mermaid圖可以去官網(wǎng)查看用例。

注意:mermaid圖在渲染時,如果不設(shè)置subgraph則可能會出現(xiàn)亂序問題,即不是按代碼中出現(xiàn)的順序渲染。

二、實現(xiàn)

為了方便Go讀取Excel,需要使用相關(guān)的Excel庫,筆者使用excelize庫。

根據(jù)前面的效果圖,可以知道,這其實就是一個深度優(yōu)先的樹,實現(xiàn)方式有兩種,一種是使用遞歸的方式來實現(xiàn),這種方式實現(xiàn)起來簡單,但是如果層次很深,那可能會出現(xiàn)棧溢出;另一種方式就是使用棧的方式來實現(xiàn),將每一層節(jié)點先壓棧,然后從棧頂取出一個節(jié)點然后再將其所有子節(jié)點入棧,再從棧頂取出一個節(jié)點處理,依此類推,直到棧中所有節(jié)點處理完畢。

下面列出使用遞歸方式實現(xiàn)的版本:

/*
MIT License

# Copyright (c) 2023 WittonBell

Permission 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 main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/xuri/excelize/v2"
)

var taskIDField string
var taskTitleField string
var preTaskField string
var noCaseSensitive bool // 是否不區(qū)分大小寫
var fieldNameRow uint    // 字段名所在行號
var dataStartRow uint    // 數(shù)據(jù)開始行號

type node struct {
	taskID    string
	taskTitle string
}

type multiMap map[string][]*node

func (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 := col
		if 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, -1
	sheetName := 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 := true
			if TaskIDCol < 0 {
				isOk = false
				fmt.Printf("要求字段名:%s\n", taskIDField)
			}
			if PreTaskIDCol < 0 {
				isOk = false
				fmt.Printf("要求字段名:%s\n", preTaskField)
			}
			if TitleCol < 0 {
				isOk = false
				fmt.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 應(yīng)用程序是將Excel任務(wù)表中的關(guān)系轉(zhuǎn)換成Markdown的mermaid圖,方便使用Markdown工具直觀地查看任務(wù)依賴。", filepath.Base(os.Args[0]))
	fmt.Fprintln(w)
	fmt.Fprintf(w, "命令格式:%s -hr [字段所在行號] -dr [數(shù)據(jù)起始行號] [-nc] -id [任務(wù)ID字段名] -t [任務(wù)標題字段名] -pid [前置任務(wù)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 string

	flag.CommandLine.Usage = usage
	flag.BoolVar(&noCaseSensitive, "nc", false, "字段名不區(qū)分大小寫")
	flag.UintVar(&fieldNameRow, "hr", 1, "字段所在行號")
	flag.UintVar(&dataStartRow, "dr", 2, "數(shù)據(jù)起始行號")
	flag.StringVar(&taskIDField, "id", "ID", "-id [任務(wù)ID字段名]")
	flag.StringVar(&taskTitleField, "t", "Title", "-t [任務(wù)標題字段名]")
	flag.StringVar(&preTaskField, "pid", "PreTask", "-pid [前置任務(wù)ID字段名]")
	flag.StringVar(&outputFile, "o", "任務(wù)圖.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
}

到此這篇關(guān)于使用Go語言生成Excel任務(wù)表依賴圖的代碼實現(xiàn)的文章就介紹到這了,更多相關(guān)Go Excel任務(wù)表依賴圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang接口IP限流,IP黑名單,IP白名單的實例

    golang接口IP限流,IP黑名單,IP白名單的實例

    這篇文章主要介紹了golang接口IP限流,IP黑名單,IP白名單的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang中錯誤處理機制詳解

    Golang中錯誤處理機制詳解

    平時在項目開發(fā)過程中少不了對錯誤的處理,一個好用的系統(tǒng)首先要確保其健壯性,不能經(jīng)常發(fā)生錯誤就卡死之類的情況,為了讓我們的程序更加健壯,我們就需要知道golang里的錯誤處理機制是怎么樣的,這篇文章帶大家一起學(xué)習(xí),需要的朋友跟著小編一起來看看吧
    2024-05-05
  • Go語言進行多時區(qū)時間轉(zhuǎn)換的示例代碼

    Go語言進行多時區(qū)時間轉(zhuǎn)換的示例代碼

    本文介紹了使用Go語言進行多時區(qū)時間轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-12-12
  • Go語言協(xié)程處理數(shù)據(jù)有哪些問題

    Go語言協(xié)程處理數(shù)據(jù)有哪些問題

    協(xié)程(coroutine)是Go語言中的輕量級線程實現(xiàn),由Go運行時(runtime)管理。本文為大家詳細介紹了Go中的協(xié)程,協(xié)程不需要搶占式調(diào)度,可以有效提高線程的任務(wù)并發(fā)性,而避免多線程的缺點
    2023-02-02
  • Go語言封裝一個Cron定時任務(wù)管理器

    Go語言封裝一個Cron定時任務(wù)管理器

    在現(xiàn)代應(yīng)用中,定時任務(wù)是非常常見的需求,無論是用于定時清理數(shù)據(jù),還是定時執(zhí)行系統(tǒng)維護任務(wù),下面我們就來使用Go語言封裝一個Cron定時任務(wù)管理器吧
    2024-12-12
  • 淺談golang package中init方法的多處定義及運行順序問題

    淺談golang package中init方法的多處定義及運行順序問題

    這篇文章主要介紹了淺談golang package中init方法的多處定義及運行順序問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • GoLand安裝與環(huán)境配置的完整步驟

    GoLand安裝與環(huán)境配置的完整步驟

    作為一個go語言程序員,覺得自己有義務(wù)為go新手開一條更簡單便捷的上手之路,下面這篇文章主要給大家介紹了關(guān)于GoLand安裝與環(huán)境配置的完整步驟,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2022-12-12
  • golang中的jwt使用教程流程分析

    golang中的jwt使用教程流程分析

    這篇文章主要介紹了golang中的jwt使用教程,接下來我們需要講解一下Claims該結(jié)構(gòu)體存儲了token字符串的超時時間等信息以及在解析時的Token校驗工作,需要的朋友可以參考下
    2023-05-05
  • Go?連接?MySQL之?MySQL?預(yù)處理詳解

    Go?連接?MySQL之?MySQL?預(yù)處理詳解

    Go語言提供了豐富的庫和工具,可以方便地連接MySQL數(shù)據(jù)庫。MySQL預(yù)處理是一種提高數(shù)據(jù)庫操作效率和安全性的技術(shù)。Go語言中的第三方庫提供了MySQL預(yù)處理的支持,通過使用預(yù)處理語句,可以避免SQL注入攻擊,并且可以提高數(shù)據(jù)庫操作的效率。
    2023-06-06
  • golang-gorm自動建表問題

    golang-gorm自動建表問題

    這篇文章主要介紹了golang-gorm自動建表問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02

最新評論

阜城县| 兖州市| 余干县| 赞皇县| 正阳县| 江油市| 澄江县| 阜城县| 毕节市| 雷山县| 当雄县| 城口县| 白玉县| 兴国县| 津南区| 定安县| 象州县| 濉溪县| 如东县| 中卫市| 五指山市| 安溪县| 芜湖县| 新疆| 石柱| 沁阳市| 班戈县| 涟源市| 和平区| 新丰县| 府谷县| 荥阳市| 南京市| 宁津县| 和硕县| 溧阳市| 肥东县| 邓州市| 吉安县| 贺兰县| 土默特右旗|