[Lua] IT技術熟練度生成器 | 根據活動記錄生成md表格 | 自創

来源:https://www.cnblogs.com/linxiaoxu/archive/2023/08/17/17637572.html
-Advertisement-
Play Games

為衡量個人能力水平自創的一套評分機制,根據時間、代碼行數、基礎理論三個變數生成。使用lua語言編寫,輸出成三個markdown表格。 ...


IT技術熟練度 v1.0

為衡量個人能力水平自創的一套評分機制,根據時間、代碼行數、基礎理論三個變數生成。最近在學lua,正好練下基本功。效果可見 個人介紹 | 代碼統計 - 小能日記 - 博客園 (cnblogs.com)

life.lua 記錄自己每日的IT活動,main.lua 程式根據life.lua生成文件 output.md

具體規則

  • 某一條目為A對象實例(如Lua),初始化40分,範圍 (0,100]

    • [0,20) 遺忘、[20,40) 生疏、[40,60) 瞭解、[60,80) 熟悉、[80,100] 熟練
  • 時間:每隔1天,進行如下模擬遺忘操作,從熟練到遺忘需要90天不敲代碼

    • 小於等於80分的條目減1分
    • 小於等於100分的條目減2分
  • 代碼行數:根據每天敲的代碼有效行數(不含空行)對熟練度增減。

    • 動態計算每個條目平均有效行AVG(4天敲800行得200),不足4天預設按500行計算
    • 當天有效行數處於 [AVG * 1.25,∞] ,加4分
    • 當天有效行數處於 [AVG * 0.75,AVG * 1.25] ,加3分
    • 當天有效行數處於 [AVG * 0.25,AVG * 0.75) ,加2分
    • 當天有效行數處於 [AVG * 0.10,AVG * 0.25) ,加1分
    • 當天有效行數處於 [AVG * 0.00,AVG * 0.10) ,加0分
    • 從50分到80分需要連續敲15天,從80分到100分需要連續敲20天。
    • 越熟練代碼越精簡,行數逐漸變少,這是量變到質變的過程,故 AVG * 0.75
  • 基礎理論:當天涉及基礎理論(如網路、演算法、設計模式、架構技巧等)

    • 集合元素個數 * 2,如["演算法"]:{"馬爾科夫鏈","迪傑斯特拉"},共2個元素,加4分
  • 狀態:狀態根據當天敲的所有行數比較 AVG = (一生敲的總行數) // (總天數),不足4天預設按500行計算

    • 加班狂魔:[1.25 * AVG,∞]、正常上班:[AVG * 0.50,AVG * 1.25]、天天摸魚:[AVG * 10,AVG * 0.50 ]
    • 其他與沒敲代碼的日子均統計到"擺爛"中。學了理論不敲等於沒學

IT活動記錄 life.lua

-- 文件 life.lua 格式如下 
life = {{
    ["date"] = "2023年8月16日", -- 日期
    ["lua"] = {484, 52}, -- 敲的有效行/註釋行
    ["c#"] = {123, 4}, -- 敲的有效行/註釋行
    ["演算法"] = {"馬爾科夫鏈", "迪傑斯特拉", "點陣圖法"}, -- 涉及的概念
    ["設計模式"] = {"單例模式"} -- 涉及的概念
}, {
    ["date"] = "2023年8月15日",
    ["lua"] = {477, 18}
}, {
    ["date"] = "2023年8月14日",
    ["lua"] = {519, 29}
}, {
    ["date"] = "2023年8月13日",
    ["lua"] = {628, 71}
}}

輸出熟練度 output.md

(如果有前置head.md則會在這拼接)
Time : 2023/8/13 ~ 2023/8/16

|技術|學習天數|熟練度 (0~100)|程度|評級|
|------|----|----|----|----|
|lua|4|50|瞭解|⭐⭐⭐|
|演算法|1|46|瞭解|⭐⭐⭐|
|c#|1|42|瞭解|⭐⭐⭐|
|設計模式|1|42|瞭解|⭐⭐⭐|

|我的一生|總共行數|天數|平均行數
|------|----|----|----|
|lua|2278|4|569|
|c#|127|1|127|

|我的狀態|天數|
|------|----|
|加班狂魔|1|
|正常上班|3|
|天天摸魚|0|
|開擺去咯|0|

部分實現

初始化

local scores = {} -- 條目的熟練度,鍵=條目,值=分數
local levelDays = {0, 0, 0, 0} -- 迄今為止效率,鍵=狀態{擺爛,摸魚,正常,高強度},值=天數
local studyDays = {} -- 每個條目各自學習了幾天
local linesCount = {} -- 每個條目各自敲了幾開始行,鍵=條目,值=行數
local startDay = nil -- 從什麼時候開始
local endDay = nil -- 從什麼時候結束 
local tmpDay = nil -- 當前記錄前一個記錄的日子

讀取記錄

主要用了一個有狀態的迭代器,配合泛型for迴圈使用。沒有用到不可變狀態和控制變數,但是個人習慣所以留著形參s、c。

-- 記錄迭代器,逐條返回記錄
function getRecords()
    local i = #life
    return function(s, c)
        if i > 0 then
            i = i - 1
            return life[i + 1]
        end
        return nil
    end, nil, nil
end

時間計算

用模式匹配捕獲年月日,並保存最早的時間和最晚的時間。另外計算兩個時間之間的間隔sep,根據間隔大小更新scores。

-- 根據每條記錄更新 startDay 跟 endDay
-- 計算前一條記錄與後一條記錄的間隔天數,統計至levelDays併進行scores每日遺忘操作
function updateTime(date)
    local y, m, d = string.match(date, "(%d+)年(%d+)月(%d+)")
    if not startDay then
        startDay = {
            year = y,
            month = m,
            day = d
        }
    end
    tmpDay = tmpDay or startDay
    endDay = {
        year = y,
        month = m,
        day = d
    }
    -- 計算前一條記錄與後一條記錄的間隔天數 sep
    local sep = os.difftime(os.time(endDay), os.time(tmpDay)) // 3600 // 24
    -- 間隔大於 1 天,將sep - 1的天數歸入擺爛,併進行每日遺忘操作
    if sep > 1 then
        updateLevelDays(1, sep - 1)
        dailyScores(sep)
    else
        dailyScores(1)
    end
    tmpDay = endDay
end

遺忘操作

根據規則對scores進行減少

-- 每日遺忘
function dailyScores(n)
    for k, v in pairs(scores) do
        if v <= 80 then
            v = v - 1 * (n or 1)
        elseif v <= 100 then
            v = v - 2 * (n or 1)
        end
        scores[k] = v <= 0 and 0 or v
    end
end

每條記錄的迭代過程

-- 根據每條記錄更新 scores levelDays linesCount
function update(record)
    local countLines = 0 -- 當天寫過的代碼行數總和
    for item, v in pairs(record) do
        -- 判斷是編程語言還是基礎理論
        if type(v[1]) == "number" then
            local total = v[1] + (v[2] or 0)
            countLines = countLines + total
            updateLinesCount(item, total)
            updateStudyDays(item)
            local level = level(item, total)
            updateScores(item, total, level)
        elseif type(v[1]) == "string" then
            updateScores(item, #v)
            updateStudyDays(item)
        else
            error("條目格式有錯", 2)
        end
    end
    -- 更新 levelDays , 保存當天狀態 {擺爛,摸魚,正常,高強度}
    updateLevelDaysByCountLines(countLines)
end

主程式

-- 主程式
for r in getRecords() do
    -- 根據當前記錄時間更新
    updateTime(r["date"])
    -- 刪除"date"減少多餘的判斷
    r["date"] = nil
    -- 更新 scores levelDays linesCount
    update(r)
end
-- 根據系統時間更新一次時間
updateTime(os.date("%Y年%m月%d日"))

排序

table.sort 只能序列使用,所以每張表先各自拷貝成一份序列再排序

-- 排個序 scores,linesCount
scoresSort = {}
for k, v in pairs(scores) do
    scoresSort[#scoresSort + 1] = {k, v}
end
linesCountSort = {}
for k, v in pairs(linesCount) do
    linesCountSort[#linesCountSort + 1] = {k, v}
end
table.sort(scoresSort, function(a, b)
    return a[2] > b[2] -- 熟練度分數對比
end)
table.sort(linesCount, function(a, b)
    return a[2] > b[2] -- 總行數對比
end)

前置拼接

如果前面還需要有固定內容,可以添加個head.md文件。如果存在則自動拼接

io.output(io.open("output.md", "w"))
-- 做前置拼接,一般是個人介紹的內容
pcall(function()
    f = io.open("head.md", "r")
    if f then
        io.write(f:read("a"))
        io.write "\n"
        f:close()
    end
end)

輸出表格

輸出表格按markdown格式進行輸出

-- 輸出各個表格
io.write("# IT技術熟練度\n")
io.write(string.format("**Time : %d/%d/%d ~ %d/%d/%d**\n", startDay.year, startDay.month, startDay.day, endDay.year,
    endDay.month, endDay.day))
io.write("|技術|學習天數|熟練度 (0~100)|程度|評級|\n")
io.write("|------|----|----|----|----|\n")
local cd = {"遺忘", "生疏", "瞭解", "熟悉", "熟練"}
for i, v in ipairs(scoresSort) do
    local stars = ""
    for i = 1, v[2] // 20 + 1, 1 do
        stars = stars .. "⭐"
    end
    io.write(string.format("\z
    |%s|%s|%d|%s|%s|\n\z
    \z", v[1], studyDays[v[1]], v[2], cd[v[2] // 20 + 1], stars))
end
io.write "\n"

完整代碼 main.lua

-- By 小能喵喵喵 2023年8月17日03:28:14
-- https://www.cnblogs.com/linxiaoxu
-- 初始化
local scores = {} -- 條目的熟練度,鍵=條目,值=分數
local levelDays = {0, 0, 0, 0} -- 迄今為止效率,鍵=狀態{擺爛,摸魚,正常,高強度},值=天數
local studyDays = {} -- 每個條目各自學習了幾天
local linesCount = {} -- 每個條目各自敲了幾開始行,鍵=條目,值=行數
local startDay = nil -- 從什麼時候開始
local endDay = nil -- 從什麼時候結束 
local tmpDay = nil -- 當前記錄前一個記錄的日子

-- 載入記錄集
dofile("life.lua")

-- 記錄迭代器,逐條返回記錄
function getRecords()
    local i = #life
    return function(s, c)
        if i > 0 then
            i = i - 1
            return life[i + 1]
        end
        return nil
    end, nil, nil
end

-- 根據每條記錄更新 startDay 跟 endDay
-- 計算前一條記錄與後一條記錄的間隔天數,統計至levelDays併進行scores每日遺忘操作
function updateTime(date)
    local y, m, d = string.match(date, "(%d+)年(%d+)月(%d+)")
    if not startDay then
        startDay = {
            year = y,
            month = m,
            day = d
        }
    end
    tmpDay = tmpDay or startDay
    endDay = {
        year = y,
        month = m,
        day = d
    }
    -- 計算前一條記錄與後一條記錄的間隔天數 sep
    local sep = os.difftime(os.time(endDay), os.time(tmpDay)) // 3600 // 24
    -- 間隔大於 1 天,將sep - 1的天數歸入擺爛,併進行每日遺忘操作
    if sep > 1 then
        updateLevelDays(1, sep - 1)
        dailyScores(sep)
    else
        dailyScores(1)
    end
    tmpDay = endDay
end

-- 更新 linesCount
function updateLinesCount(item, count)
    linesCount[item] = (linesCount[item] or 0) + count
end

-- 更新 studyDays
function updateStudyDays(item)
    studyDays[item] = (studyDays[item] or 0) + 1
end

-- 每日遺忘
function dailyScores(n)
    for k, v in pairs(scores) do
        if v <= 80 then
            v = v - 1 * (n or 1)
        elseif v <= 100 then
            v = v - 2 * (n or 1)
        end
        scores[k] = v <= 0 and 0 or v
    end
end

-- 根據 linesCount 對比得出狀態{擺爛,摸魚,正常,高強度},在updateLinesCount之後執行
function level(item, count)
    local avg = nil
    if studyDays[item] < 4 then -- 不足4天預設按500行計算
        avg = 500
    else
        avg = linesCount[item] // studyDays[item]
    end
    if count >= avg * 1.25 then
        return 4
    elseif count >= avg * 0.75 then
        return 3
    elseif count >= avg * 0.25 then
        return 2
    elseif count >= avg * 0.1 then
        return 1
    else
        return 0
    end
end

-- 更新 levelDays, level = 1~4
function updateLevelDays(level, sep)
    levelDays[level] = (levelDays[level] or 0) + (sep or 1)
end

-- 根據當天寫的代碼更新 levelDays
function updateLevelDaysByCountLines(countLines)
    local totalLines = 0
    local totalDays = 0
    for _, v in pairs(linesCount) do
        totalLines = v + totalLines
    end
    for _, v in pairs(studyDays) do
        totalDays = v + totalDays
    end
    local avgLines = totalLines // totalDays
    if totalDays < 4 then -- 總天數不足4天按500行算
        avgLines = 500
    end
    if countLines > 1.25 * avgLines then
        updateLevelDays(4)
    elseif countLines > 0.5 * avgLines then
        updateLevelDays(3)
    elseif countLines > 0.1 * avgLines then
        updateLevelDays(2)
    else
        updateLevelDays(1)
    end
end

-- 根據條目鍵值對加分,並更新linesCount
function updateScores(item, count, level)
    scores[item] = scores[item] or 40
    if level then
        if level == 4 then
            scores[item] = scores[item] + 4
        elseif level == 3 then
            scores[item] = scores[item] + 3
        elseif level == 2 then
            scores[item] = scores[item] + 2
        elseif level == 1 then
            scores[item] = scores[item] + 1
        else
            -- do nothing
        end
        scores[item] = scores[item] > 100 and 100 or scores[item]
    else
        scores[item] = scores[item] + count * 2
        scores[item] = scores[item] > 100 and 100 or scores[item]
    end
end

-- 根據每條記錄更新 scores levelDays linesCount
function update(record)
    local countLines = 0 -- 當天寫過的代碼行數總和
    for item, v in pairs(record) do
        -- 判斷是編程語言還是基礎理論
        if type(v[1]) == "number" then
            local total = v[1] + (v[2] or 0)
            countLines = countLines + total
            updateLinesCount(item, total)
            updateStudyDays(item)
            local level = level(item, total)
            updateScores(item, total, level)
        elseif type(v[1]) == "string" then
            updateScores(item, #v)
            updateStudyDays(item)
        else
            error("條目格式有錯", 2)
        end
    end
    -- 更新 levelDays , 保存當天狀態 {擺爛,摸魚,正常,高強度}
    updateLevelDaysByCountLines(countLines)
end

-- 主程式
for r in getRecords() do
    -- 更新 startDay 跟 endDay
    updateTime(r["date"])
    -- 刪除"date"減少多餘的判斷
    r["date"] = nil
    -- 更新 scores levelDays linesCount
    update(r)
end
-- 根據系統時間更新一次時間
updateTime(os.date("%Y年%m月%d日"))

-- 排個序 scores,linesCount
scoresSort = {}
for k, v in pairs(scores) do
    scoresSort[#scoresSort + 1] = {k, v}
end
linesCountSort = {}
for k, v in pairs(linesCount) do
    linesCountSort[#linesCountSort + 1] = {k, v}
end
table.sort(scoresSort, function(a, b)
    return a[2] > b[2] -- 熟練度分數對比
end)
table.sort(linesCount, function(a, b)
    return a[2] > b[2] -- 總行數對比
end)

-- 輸出結果
io.output(io.open("output.md", "w"))
-- 做前置拼接,一般是個人介紹的內容
pcall(function()
    f = io.open("head.md", "r")
    if f then
        io.write(f:read("a"))
        io.write "\n"
        f:close()
    end
end)
-- 輸出各個表格
io.write("# IT技術熟練度\n")
io.write(string.format("**Time : %d/%d/%d ~ %d/%d/%d**\n", startDay.year, startDay.month, startDay.day, endDay.year,
    endDay.month, endDay.day))
io.write("|技術|學習天數|熟練度 (0~100)|程度|評級|\n")
io.write("|------|----|----|----|----|\n")
local cd = {"遺忘", "生疏", "瞭解", "熟悉", "熟練"}
for i, v in ipairs(scoresSort) do
    local stars = ""
    for i = 1, v[2] // 20 + 1, 1 do
        stars = stars .. "⭐"
    end
    io.write(string.format("\z
    |%s|%s|%d|%s|%s|\n\z
    \z", v[1], studyDays[v[1]], v[2], cd[v[2] // 20 + 1], stars))
end
io.write "\n"
io.write("# 這一生寫過的代碼\n")
io.write("|我的一生|總共行數|天數|平均行數\n")
io.write("|------|----|----|----|\n")
for i, v in ipairs(linesCountSort) do
    io.write(string.format("|%s|%d|%d|%d|\n", v[1], v[2], studyDays[v[1]], v[2] // studyDays[v[1]]))
end
io.write "\n"
io.write("# 這一生所處的狀態\n")
io.write("|我的狀態|天數|\n")
io.write("|------|----|\n")
io.write(string.format("|加班狂魔|%d|\n", levelDays[4]))
io.write(string.format("|正常上班|%d|\n", levelDays[3]))
io.write(string.format("|天天摸魚|%d|\n", levelDays[2]))
io.write(string.format("|開擺去咯|%d|\n", levelDays[1]))
io.write("\n")
io.write("# 這一生活動的統計\n")
io.write("使用插件 `VS Code Counter` 進行每日統計,僅統計有效行,不含空行\n")
io.write("```lua\n")
f = io.open("life.lua")
io.write(f:read("a"))
f:close()
io.write("```\n")
io.output():close()

示例效果

Time : 2023/8/13 ~ 2023/8/16

技術 學習天數 熟練度 (0~100) 程度 評級
lua 4 50 瞭解 ⭐⭐⭐
演算法 1 46 瞭解 ⭐⭐⭐
c# 1 42 瞭解 ⭐⭐⭐
設計模式 1 42 瞭解 ⭐⭐⭐
我的一生 總共行數 天數 平均行數
lua 2278 4 569
c# 127 1 127
我的狀態 天數
加班狂魔 1
正常上班 3
天天摸魚 0
開擺去咯 0

如何獲取行數

image-20230817145436146


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文通過簡單的示例代碼和說明,讓讀者能夠瞭解Mybatis-Plus+Mysql的簡單使用 必須說明的是,本文有部分內容是為了後續的微服務寫的,所以如果只想用Mybatis-Plus的話,直接使用bank1項目即可 1.新建父項目,選用spring initializr即可,可以刪除其他文件,僅僅留 ...
  • ![](https://cdn.nlark.com/yuque/0/2023/jpeg/28753938/1691067189459-f51a48da-0da6-4e6e-aeee-75b39662cd20.jpeg) ## 一、Lambda表達式 > Lambda 是一個匿名函數,我們可以把 La ...
  • 關於票據系統設計在之前的博客中也聊過,今天做一個補充 1、架構 票據系統主要就是和票交所進行交互,圍繞這一核心,我們把系統劃分為三大部分,分別是:票據網關服務、票據業務服務、票據庫存服務。 網關服務:對接票交所,負責和票交所的交互,主要是收發報文。 業務服務:負責票據業務的處理,比如出票、背書、貼現 ...
  • # 【狂神說Java】Java零基礎學習筆記-JavaSE總結 ## JavaSE總結: ![image](https://img2023.cnblogs.com/blog/3231511/202308/3231511-20230817171925456-1307925972.jpg) ## 🎉� ...
  • # 【狂神說Java】Java零基礎學習筆記-異常 ## 異常01:Error和Exception ### 什麼是異常 - 實際工作中,遇到的情況不可能是非常完美的。比如:你寫的某個模塊,用戶輸入不一定符合你的要求、你的程式要打開某個文件,這個文件可能不存在或者文件格式不對,你要讀取資料庫的數據,數 ...
  • ### 1. json.load(json_data)與json.dump(python_data) json.load()用來將讀取json文件,json.dump()用來將數據寫入json文件 ### 2. json.loads()與json.dumps() - json.dumps 將 Pyt ...
  • 使用python爬蟲爬取數據的時候,經常會遇到一些網站的反爬蟲措施,一般就是針對於headers中的User-Agent,如果沒有對headers進行設置,User-Agent會聲明自己是python腳本,而如果網站有反爬蟲的想法的話,必然會拒絕這樣的連接。 而修改headers可以將自己的爬蟲腳本 ...
  • C++ STL(Standard Template Library)是C++標準庫中的一個重要組成部分,提供了豐富的模板函數和容器,用於處理各種數據結構和演算法。在STL中,排序、算數和集合演算法是常用的功能,可以幫助我們對數據進行排序、統計、查找以及集合操作等。STL提供的這些演算法,能夠滿足各種數據處... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...