為衡量個人能力水平自創的一套評分機制,根據時間、代碼行數、基礎理論三個變數生成。使用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分
- 集合元素個數 * 2,如
-
狀態:狀態根據當天敲的所有行數比較 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 |