Lua程式設計第四版第二部分編程實操自做練習題答案,帶:star:為重點。 ## 14.1 :star: > 該函數用於兩個稀疏矩陣相加 ```lua function martixAdd(a, b) local c = {} for i = 1, #a, 1 do c[i] = {} for k, ...
Lua程式設計第四版第二部分編程實操自做練習題答案,帶⭐為重點。
14.1 ⭐
該函數用於兩個稀疏矩陣相加
function martixAdd(a, b)
local c = {}
for i = 1, #a, 1 do
c[i] = {}
for k, v in pairs(a[i]) do
c[i][k] = v
end
end
for i = 1, #b, 1 do
for k, v in pairs(b[i]) do
c[i][k] = (c[i][k] or 0) + v
c[i][k] = (c[i][k] ~= 0) and c[i][k] or nil
end
end
return c
end
A = {{
[5] = 1
}, {}, {
[1] = 3,
[3] = 4
}, {}, {
[4] = -1
}}
B = {{
[2] = 2
}, {}, {
[1] = -3,
[4] = -3
}, {
[3] = 8
}, {
[1] = 7
}}
for k, v in pairs(martixAdd(A, B)) do
for j, i in pairs(v) do
print(k .. "行", j .. "列", i)
end
end
14.2
改寫隊列的實現,使得當隊列為空時兩個索引都返回0
function listNew()
return {
_first = 0,
_last = -1,
first = function()
if _first > _last then
return 0
end
return _first
end,
last = function()
if _first > _last then
return 0
end
return _last
end
}
end
l = listNew()
-- 模擬空隊列
l._first = 10
l._last = 9
print(l.first, l.last)
14.3
修改圖所用的數據結構,使得圖可以保存每條邊的標簽。該數據結構應該使用包括兩個欄位的對象來表示每一條邊,即邊的標簽和邊指向的節點。與鄰接集合不同,每一個節點保存的是從當前節點出發的邊的集合。
local function name2node(graph, name)
local node = graph[name]
if not node then
graph[name] = {
name = name,
edges = {}
}
node = graph[name]
end
return node
end
function readgraph(file)
local graph = {}
f = io.open(file, 'r')
for l in f:lines() do
local name1, name2, edgeLabel = string.match(l, "(%S+)%s+(%S+)%s+(%S+)")
local from = name2node(graph, name1)
local to = name2node(graph, name2)
table.insert(from.edges, {
["label"] = edgeLabel,
["adj"] = to
})
end
return graph
end
graph = readgraph("graph.txt")
for k, v in pairs(graph) do
for _, v in pairs(v.edges) do
print(k, v.label, v.adj.name)
end
end
[[
C 1 D
C 3 E
D 1 C
D 7 E
A 4 B
A 2 D
B 1 D
B 4 C
]]
A B 4
A D 2
B D 1
D C 1
C D 1
B C 4
D E 7
C E 3
14.4 ⭐
使用14.3的表示方式,其中邊的標簽代表兩個終端節點之間的距離。該函數使用Dijkstra演算法尋找兩個指定節點之間的最短路徑。
local function name2node(graph, name)
local node = graph[name]
if not node then
graph[name] = {
name = name,
edges = {}
}
node = graph[name]
end
return node
end
function readgraph(file)
local graph = {}
f = io.open(file, 'r')
for l in f:lines() do
local name1, name2, edgeLabel = string.match(l, "(%S+)%s+(%S+)%s+(%S+)")
local from = name2node(graph, name1)
local to = name2node(graph, name2)
table.insert(from.edges, {
["label"] = edgeLabel,
["adj"] = to
})
end
return graph
end
graph = readgraph("graph.txt")
for k, v in pairs(graph) do
for _, v in pairs(v.edges) do
print(k, v.label, v.adj.name)
end
end
function Dijkstra(graph, a, b)
-- 點不存在
if not graph[a] or not graph[b] then
return nil
end
local D = {}
local T = {}
-- D 加入起點
D[graph[a]] = 0
-- T 加入其他點,初始化可達最短距離為正無窮大
for name, node in pairs(graph) do
if name ~= a then
T[node] = math.maxinteger
end
end
-- 當D中不包含b點時迴圈
while not D[graph[b]] do
-- 根據 D 迭代 T 可達最短距離
for node, d in pairs(D) do
for _, edge in pairs(node.edges) do
if not D[edge.adj] then
local newD = d + tonumber(edge.label)
T[edge.adj] = math.min(T[edge.adj], newD)
end
end
end
-- 選擇 T 可達距離最小的點
local tmp = nil
for node, d in pairs(T) do
tmp = tmp or node
if d < T[tmp] then
tmp = node
end
end
-- 如果沒有點被選中,退出迴圈
if T[tmp] == math.maxinteger then
return nil
end
-- 將前面選擇到的點加入D,並從T刪除
D[tmp] = T[tmp]
T[tmp] = nil
end
return D[graph[b]]
end
d = Dijkstra(graph, "A", "E")
if not d then
print("無路徑")
end
print(d)
-- 具體路徑計算為 A->E 路徑距離 - (X->E 路徑距離) == A -> X 路徑距離
-- 即 A->X->E
15.1~15.2
修改序列化函數,使其帶縮進地輸出嵌套表,並添加["key"]=value的語法
function serialize(o, tab)
tab = tab or 1
local t = type(o)
if t == "number" or t == "string" or t == "boolean" or t == "nil" then
io.write(string.format("%q", o))
elseif t == "table" then
io.write("{\n")
for k, v in pairs(o) do
io.write(string.rep(" ", tab))
io.write(" [", string.format("%s", serialize(k)), "] = ")
serialize(v, tab + 1)
io.write(",\n")
end
io.write(string.rep(" ", tab))
io.write("}")
else
error("cannot serialize a" .. t)
end
end
A = {
B = {
E = "dog",
H = {
F = "PIG",
G = "goose"
}
},
C = "apple",
D = 114514,
I = {1, 2, 3, {4, 5, 6}}
}
serialize(A)
[[
{
["C"] = "apple",
["D"] = 114514,
["I"] = {
[1] = 1,
[2] = 2,
[3] = 3,
[4] = {
[1] = 4,
[2] = 5,
[3] = 6,
},
},
["B"] = {
["E"] = "dog",
["H"] = {
["G"] = "goose",
["F"] = "PIG",
},
},
}
]]
15.3
修改15.2,使其只在必要時,當鍵為字元串而不是合法標識符時才使用形如["key"]=value的語法
function serialize(o, tab)
tab = tab or 1
local t = type(o)
if t == "number" or t == "string" or t == "boolean" or t == "nil" then
io.write(string.format("%q", o))
elseif t == "table" then
io.write("{\n")
for k, v in pairs(o) do
io.write(string.rep(" ", tab))
if type(k) == "string" and string.match(k, "[%a_]+[%w_]*") then
io.write(" ", k, " = ")
else
io.write(" [")
serialize(k)
io.write("] = ")
end
serialize(v, tab + 1)
io.write(",\n")
end
io.write(string.rep(" ", tab))
io.write("}")
else
error("cannot serialize a" .. t)
end
end
A = {
B = {
E = "dog",
H = {
F = "PIG",
G = "goose"
}
},
C = "apple",
D = 114514,
I = {1, 2, 3, {4, 5, 6}}
}
serialize(A)
[[
{
C = "apple",
D = 114514,
I = {
[1] = 1,
[2] = 2,
[3] = 3,
[4] = {
[1] = 4,
[2] = 5,
[3] = 6,
},
},
B = {
E = "dog",
H = {
G = "goose",
F = "PIG",
},
},
}
]]
15.4
使其在可能時使用列表的構造器語法。例如應將表{14,15,19}序列化為{14,15,19},而不是{[1] =14,[2]=15,[3]=19},在遍歷該部分的時候不要重覆序列化。
function serialize(o)
local typ = type(o)
if typ == "number" or typ == "string" or typ == "boolean" or typ == "nil" then
io.write(string.format("%q", o))
elseif typ == "table" then
io.write("{\n")
local hash = {}
-- 遍歷列表併進行hash記錄
for i, v in ipairs(o) do
serialize(v)
io.write(",")
hash[i] = true
end
_ = (#hash ~= 0) and io.write("\n") or nil
-- 遍歷除列表之外的所有元素
for k, v in pairs(o) do
if type(k) == "string" and string.match(k, "[%a_]+[%w_]*") then
io.write(" ", k, " = ")
elseif hash[k] == true then
goto continue
-- do nothing
else
io.write(" [")
serialize(k)
io.write("] = ")
end
serialize(v)
io.write(",\n")
::continue::
end
io.write("}")
else
error("cannot serialize a" .. t)
end
end
A = {
B = {
E = "dog",
H = {
F = "PIG",
G = "goose"
}
},
C = "apple",
D = 114514,
I = {1, 2, 3, {4, 5, 6}},
[1] = 4,
[2] = 3,
[3] = 3,
[5] = 4
}
serialize(A)
TEST = {
4,
3,
3,
[5] = 4,
C = "apple",
D = 114514,
I = {1, 2, 3, {4, 5, 6}},
B = {
E = "dog",
H = {
G = "goose",
F = "PIG"
}
}
}
[[
{
4,3,3,
[5] = 4,
C = "apple",
D = 114514,
I = {
1,2,3,{
4,5,6,
},
},
B = {
E = "dog",
H = {
G = "goose",
F = "PIG",
},
},
}
]]
16.1
通常,在載入代碼段時增加一些首碼很有用。該函數類似於函數load,不過會將第一個參數增加到待載入的代碼段之前。
像原始的load函數一樣,函數loadwithprefix應該既可以接收字元串形式的代碼段,也可以通過函數進行讀取。即使待載入的代碼段是字元串形式的,函數loadwithprefix也不應該進行實際的字元串連接操作。相反,它應該調用函數load並傳入一個恰當的讀取函數來實現功能,這個讀取函數首先返回要增加的代碼,然後返回原始的代碼段。
function loadWithPrefix(s, f)
local tmp = 1
return load(function()
if tmp == 1 then
tmp = 2
return s
elseif tmp == 2 then
if type(f) == "string" then
tmp = 3
return f
elseif type(f) == "function" then
return f()
end
else
return nil
end
end)
end
f = loadWithPrefix("local x = 100;", "print(x)")
f()
f = loadWithPrefix("local x = 100;", io.lines('load.txt', '*L'))
f()
16.2 ⭐
請編寫一個函數mulitiload,該函數接收一組字元串或函數來生成函數。其他規則與16.1類似。
function multiLoad(...)
local t = {...}
local i = 1
return load(function()
::continue::
if i <= #t then
local v = t[i]
if type(v) == "string" then
i = i + 1
return v
elseif type(v) == "function" then
local tmp = v()
if tmp then
return tmp
else
i = i + 1
goto continue -- 進行下一回合
end
end
else
return nil
end
end)
end
f = multiLoad("local x = 100;", "print(x)", "x = 10; print(x);")
f()
f = multiLoad("local x = 100;", io.lines('load.txt', '*L'), "x = 10; print(x);")
f()
16.3 ⭐
該函數對於指定的n返回特定版本的函數stringrep_n。在實現方面不能使用閉包,而是應該構造出包含合理指令序列的Lua代碼,然後再使用函數load生成最終的函數。請比較通用版本的stringrep函數與我們自己實現的版本之間的性能差異
自己實現的版本重覆次數是固定的,省去了計算各組合次數的性能開銷,速度更快。
function stringrepN(n)
local top = [[local s = ...;local r = "";]]
local middle = ""
local tail = "r=r..s;return r;"
local count = 0
local tmp = n
while n // 2 ~= 0 do
count = count + 1
n = n // 2
middle = middle .. 's=s..s;'
end
for i = 1, tmp - 2 ^ count, 1 do
top = top .. 'r=r..s;'
end
return load(top .. middle .. tail)
end
stringrep = stringrepN(256)
start = os.clock()
stringrep("abcdefg", 2 ^ 25)
print(os.clock() - start)
start = os.clock()
string.rep("abcdefg", 2 ^ 25)
print(os.clock() - start)
print(string.rep("a", 256) == stringrep("a"))
[[
0.0
0.321
true
]]
16.4
你能想到一個使pcall(pcall,f)的第1個返回值為false的f?
function b(x)
print(x)
end
-- 一個函數和要傳入的參數
ok, msg = pcall(b, 100)
print(ok, msg)
io.write("\n")
ok, msg = pcall(nil) -- 有錯誤信息但未引發錯誤
print(ok, msg)
io.write("\n")
ok, msg = pcall(pcall, nil) -- 錯誤信息返回了false
print(ok, msg)
io.write("\n")
ok, msg = pcall(pcall, (function()
end)()) -- 連nil也不返回的空value
print(ok, msg)
io.write("\n")
17.2
將幾何區域系統的實現,9.4重寫為恰當的模塊
mod = require 'mod'
circle = mod.disk(0, 0, 0.5)
circle2 = mod.translate(circle, -0.3, 0.2)
shape = mod.difference(circle, circle2)
local plot = mod.plot
plot(shape, 512, 512, "test.pbm")
M = {}
function M.disk(cx, cy, r)
return function(x, y)
return (x - cx) ^ 2 + (y - cy) ^ 2 <= r ^ 2
end
end
function M.rect(left, right, top, bottom)
return function(x, y)
return left <= x and x <= right and bottom <= y and y <= top
end
end
function M.complement(r)
return function(x, y)
return not r(x, y)
end
end
function M.union(r1, r2)
return function(x, y)
return r1(x, y) or r2(x, y)
end
end
function M.intersection(r1, r2)
return function(x, y)
return r1(x, y) and r2(x, y)
end
end
function M.difference(r1, r2)
return function(x, y)
return r1(x, y) and not r2(x, y)
end
end
function M.translate(r, dx, dy)
return function(x, y)
return r(x - dx, y - dy)
end
end
function M.plot(r, M, N, file)
f = io.open(file, "w")
f:write("P1\n", M, " ", N, "\n")
for i = 1, N, 1 do
local y = (N - i * 2) / N
for j = 1, M do
local x = (j * 2 - M) / M
f:write(r(x, y) and "1" or "0")
end
f:write("\n")
end
f:close()
end
return M
17.3
如果在lua的搜索路徑中添加一項絕對路徑的文件名,會導致require一個不存在的模塊時,這個模塊都會搜索到固定文件名的文件作為它導入的模塊
而且模塊名不同,在package.loaded都會緩存,載入相同固定路徑的module,在記憶體中也是兩份獨立的副本
在極端情況下有用,相當於始終會有預設的模塊被載入,可以用這套機制來拓展require函數
https://www.lua.org/pil/8.1.html
17.4
編寫一個同時搜索lua文件和C標準庫的搜索器
function searcher(file, path)
if type(file) ~= "string" or type(path) ~= "string" then
return nil, error("not string type")
end
file = package.searchpath(file, path)
if not file then
return nil, error("no file")
end
f = loadfile(file)
if not f then
f = package.loadlib(file)
end
if not f then
return nil, error("failed load")
end
return f(), nil
end
local m = searcher("mod", "./?.lua;./?.so;/usr/lib/lua5.2/?.so;/usr/share/lua5.2/?.lua")
print(m.rect(1, 1, 1, 1))