《Lua程序设计第四版》 第二部分14~17章自做练习题答案

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算法寻找两个指定节点之间的最短路径。

image-20230815163212132
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))
posted @ 2023-08-15 21:43  小能日记  阅读(32)  评论(0编辑  收藏  举报