《Lua程序设计第四版》 第三部分18~21章自做练习题答案

Lua程序设计第四版第三部分编程实操自做练习题答案,带⭐为重点。

18.1

编写一个迭代器,与数值型for等价,能否以无状态迭代器实现?

function fromto(n, m)
    return function(m, c)
        if c < m then
            return c + 1
        end
        return nil
    end, m, n - 1
end

for i in fromto(1, 10) do
    print(i)
end

18.2 ⭐

为18.1迭代器添加一个步进参数

function fromto(n, m, step)
    step = step or 1
    return function(m, c)
        if c < m then
            return c + step
        end
        return nil
    end, m, n - step
end

for i in fromto(1, 3) do
    print(i)
end
io.write("\n")
for i in fromto(1, 10, 3) do
    print(i)
end

18.3

请编写一个迭代器uniquewords,该迭代器返回指定文件中没有重复的所有单词

function uniquewords(filename)
    local f = io.open(filename, "r")
    local words = {}
    local index = 1
    for l in f:lines() do
        while true do
            w, i = string.match(l, "(%w+)()", index)
            -- 忽略大小写
            if w then
                w = string.lower(w)
                words[w] = (words[w] or 0) + 1
                index = i
            else
                index = 1
                break
            end
        end
    end
    local w = {}
    for k, v in pairs(words) do
        if v == 1 then
            w[#w + 1] = k
        end
    end
    -- 返回依赖于 index、w 两个状态的迭代器
    index = 0
    return function()
        index = index + 1
        return w[index]
    end
end

for w in uniquewords("article.txt") do
    print(w)
end

18.4

function splitAll(s)
    words = {}
    for i = 1, #s, 1 do -- 截取长度
        for j = 1, #s - i + 1, 1 do -- 截取开始位置
            words[string.sub(s, j, j + i - 1)] = true
        end
    end
    local index = 1
    return next, words, nil
end

for w in splitAll("aaab") do
    print(w)
end

18.5 ⭐

-- 位图法 假设集合<=64个元素
function allSubset(t)
    local bitmap = 0
    local x = {} -- 根据t创建一个有序的序列
    local res = {} -- 固定的返回表,不创建新表
    for k, _ in pairs(t) do
        x[#x + 1] = k
    end
    return function()
        if bitmap >> #x ~= 0 then
            return nil
        end
        for i, v in ipairs(x) do
            if (bitmap >> (i - 1)) & 1 == 1 then
                res[v] = true
            else
                res[v] = nil
            end
        end
        bitmap = bitmap + 1
        return res
    end
end

for res in allSubset({
    [1] = true,
    [2] = true,
    [3] = true,
    ["X"] = true
}) do
    io.write "{"
    for k, v in pairs(res) do
        io.write(k, ",")
    end
    io.write "}"
    io.write "\n"
end

19.1 ⭐

使马尔科夫链算法更加通用,以支持任意长度的前缀单词序列

利用队列实现

function prefix(pack)
    return table.concat(pack, " ")
end

-- 词迭代器
function findWord(filename)
    local f = io.open(filename, "r")
    local pos = 1
    local l = f:read("l")
    return function()
        while l do
            while true do
                w, index = string.match(l, "(%w+[,.;:]?)()", pos)
                if w then
                    pos = index
                    return w
                else
                    pos = 1
                    l = f:read("l")
                    break
                end
            end
        end
        return nil
    end, nil, nil
end

-- for w in findWord("article.txt") do
--     print(w)
-- end

-- 生成n个前缀和对应该前缀的单词集合
function createW(filename, n)
    local W = {}
    local i = 0
    local pack = {}
    for i = 1, n, 1 do
        pack[i] = "\n"
    end

    for w in findWord(filename) do
        local prefix = prefix(pack)
        W[prefix] = W[prefix] or {}
        table.insert(W[prefix], w)
        table.remove(pack, 1)
        table.insert(pack, w)
    end
    -- 给文章末尾的n个词添加NOWORD
    local prefix = prefix(pack)
    W[prefix] = W[prefix] or {}
    table.insert(W[prefix], "\n")
    return W
end

function markovChain(filename, n, MAXGEN)
    W = createW(filename, n)
    local pack = {}
    local res = {}
    for i = 1, n, 1 do
        pack[i] = "\n"
    end
    while #res ~= MAXGEN do
        local words = W[prefix(pack)]
        local w = words[math.random(1, #words)]
        -- 循环到原文末尾时退出循环
        if w == "\n" then
            return
        end
        res[#res + 1] = w
        table.remove(pack, 1)
        table.insert(pack, w)
    end
    return table.concat(res, " ")
end

res = markovChain("article.txt", 2, 200)
print(res)

-- W = createW("article.txt", 2)
-- for k, v in pairs(W) do
--     print(k)
--     io.write "\n"
-- end

20.1

请定义一个元方法__sub,该元方法用于计算两个集合的差集

local Set = {}
local mt = {}

function Set.new(list)
    local set = {}
    setmetatable(set, mt)
    for i, v in ipairs(list) do
        set[v] = true
    end
    return set
end

function Set.union(a, b)
    local set = Set.new {}
    for k, _ in pairs(a) do
        set[k] = true
    end
    for k, _ in pairs(b) do
        set[k] = true
    end
    return set
end

function Set.intersection(a, b)
    local set = Set.new {}
    for k, _ in pairs(a) do
        set[k] = b[k]
    end
    return set
end

mt.__tostring = function(set)
    local t = {}
    for k, v in pairs(set) do
        t[#t + 1] = k
    end
    return table.concat(t, " | ")
end

mt.__sub = function(a, b)
    if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
        error("不是集合类型", 2)
    end
    c = Set.new {}
    for k, _ in pairs(a) do
        c[k] = true
    end
    for k, _ in pairs(b) do
        c[k] = nil
    end
    return c
end

a = Set.new({1, 2, 3, 4, 5})
b = Set.new({1, 2, 3})
c = a - b
print(a)
print(b)
print(c)
print(c - Set.new {1, 2, 3, 4, 5}) -- 空集

20.2

请定义一个元方法__len,该原方法用于实现使用#s计算集合s中的元素个数

local Set = {}
local mt = {}

function Set.new(list)
    local set = {}
    setmetatable(set, mt)
    for i, v in ipairs(list) do
        set[v] = true
    end
    return set
end

mt.__len = function(set)
    local count = 0
    for _, _ in pairs(set) do
        count = count + 1
    end
    return count
end

a = Set.new({1, 2, 3, 4, 5})
b = Set.new({1, 2, 3})
c = Set.new({})
print(#a)
print(#b)
print(#c)

20.3 ⭐

实现只读表的另一种方式是将一个函数用作__index元方法。这种方法使得访问的开销更大,但是创建只读表的开销更小(因为所有的只读表能够共享同一个元表)。请使用这种方式重写函数readOnly

把表的地址保存到各自的代理表中,即proxy.t

local mt = {}

mt.__index = function(t, k)
    return t.t[k]
end

mt.__newindex = function()
    error("readonly", 2)
end

-- 正常表 -> 只读表 , 而不是修改正常表的元表
function readOnly(t)
    proxy = {}
    proxy.t = t
    setmetatable(proxy, mt)
    return proxy
end

t1 = readOnly({
    mode = "fast"
})

t2 = readOnly({1, 2, 3})

print(t1.x)
print(t1.mode)
ok, msg = pcall(function()
    t1.x = 1
end)
print(ok, msg)

print(t2[1])
pcall(function()
    t2[4] = 4
end)
print(ok, msg)

20.4

代理表可以表示除表外的其他类型的对象。编写函数fileAsArray,该函数以一个文件名为参数,返回值为对应文件的代理,当执行 t=fileAsArray("myFile")后,访问t[i]返回指定文件的第i个字节,而对t[i]的赋值更新第i个字节。

function fileAsArray(filename)
    local proxy = {}
    local mt = {}
    setmetatable(proxy, mt)
    local f = io.open(filename, "r+")
    mt.__index = function(_, k)
        if type(k) ~= "number" then
            error("not a number", 2)
        end
        f:seek("set", k - 1)
        return f:read(1)
    end
    mt.__newindex = function(_, k, v)
        if type(k) ~= "number" then
            error("not a number", 2)
        end
        if type(v) ~= "string" or #v ~= 1 then
            error("not a byte string", 2)
        end
        f:seek("set", k - 1)
        f:write(v)
    end
    return proxy
end

proxy = fileAsArray("update.txt")
print(proxy[1])
print(proxy[2])
print(proxy[3])
proxy[1] = "p"
proxy[2] = "i"
proxy[3] = "g"
print(proxy[1])
print(proxy[2])
print(proxy[3])

20.5 ⭐

扩展之前的示例,使得我们能够使用pairs(t)遍历一个文件中的所有字节,并使用#t来获得文件的大小。

function fileAsArray(filename)
    local proxy = {}
    local mt = {}
    setmetatable(proxy, mt)
    local f = io.open(filename, "r+")
    mt.__index = function(_, k)
        if type(k) ~= "number" then
            error("not a number", 2)
        end
        f:seek("set", k - 1)
        return f:read(1)
    end
    mt.__newindex = function(_, k, v)
        if type(k) ~= "number" then
            error("not a number", 2)
        end
        if type(v) ~= "string" or #v ~= 1 then
            error("not a byte string", 2)
        end
        f:seek("set", k - 1)
        f:write(v)
    end
    return proxy
end

proxy = fileAsArray("update.txt")
print(proxy[1])
print(proxy[2])
print(proxy[3])
proxy[1] = "p"
proxy[2] = "i"
proxy[3] = "g"
print(proxy[1])
print(proxy[2])
print(proxy[3])

21.1

实现一个类Stack,该类具有方法push、pop、top和isempty。

Stack = {}

function Stack:new(o)
    o = o or {}
    self.__index = self
    setmetatable(o, self)
    return o
end

function Stack:push(v)
    table.insert(self, v)
end

function Stack:pop()
    return table.remove(self, #self)
end

function Stack:top()
    return self[#self]
end

function Stack:isEmpty()
    return #self == 0
end

s = Stack:new({})
print(s:isEmpty())
s:push(1)
s:push(2)
s:push(3)
print(s:top())
print(s:pop())
print(s:pop())
print(s:pop())

21.2 ⭐

实现类Stack的子类StackQueue。除了继承的方法外,还给这个子类添加一个方法insertbottom,该方法在栈的底部插入一个元素(这个方法使得我们可以把这个类的实例用作队列)

Stack = {}

function Stack:new(o)
    o = o or {}
    self.__index = self
    setmetatable(o, self)
    return o
end

function Stack:push(v)
    table.insert(self, v)
end

function Stack:pop()
    return table.remove(self, #self)
end

function Stack:top()
    return self[#self]
end

function Stack:isEmpty()
    return #self == 0
end

StackQueue = Stack:new({})
function StackQueue:insertbottom(v)
    table.insert(self, 1, v)
end

s = StackQueue:new({})
print(s:isEmpty())
s:insertbottom(2)
s:insertbottom(3)
s:insertbottom(4)
s:push(1)
print(s:pop())
print(s:pop())
print(s:pop())
print(s:pop())

21.3

使用对偶表示重新实现类Stack

对偶表示就是建一个私有表,把各个对象当做键,用于实现 私有性,缺点是被当做键的表不显式移除就永远不会被当成垃圾释放。

local stack = {}

Stack = {}

function Stack:new(o)
    o = o or {}
    self.__index = self
    setmetatable(o, self)
    stack[o] = {}
    return o
end

function Stack:push(v)
    table.insert(stack[self], v)
end

function Stack:pop()
    return table.remove(stack[self], #stack[self])
end

function Stack:top()
    return stack[self][#stack[self]]
end

function Stack:isEmpty()
    return #stack[self] == 0
end

s = Stack:new({})
print(s:isEmpty())
s:push(1)
s:push(2)
s:push(3)
print(s:top())
print(s:pop())
print(s:pop())
print(s:pop())

21.4 ⭐

对偶表示的一种变形式使用代理表示对象。每一个对象由一个空的代理表表示,一个内部的表把代理映射到保存对象状态的表。这个内部表不能从外部访问,但是方法可以使用内部表来把self变量转换为要操作的真正的表。请使用这种方式实现银行账户的示例,然后讨论这种方式的优点和缺点。

私有性,原有对象被映射为一个代理表,只能通过代理表给定的方法修改原有对象,不能直接修改原有对象。

每个对象都要生成一个对应的代理表,比较耗性能。

Account = {}
local account = {} -- 对偶表示的表

function Account:new(o)
    local proxy = {}
    account[proxy] = o -- 代理表 与 保存对象状态表的映射
    self.__index = self
    setmetatable(proxy, self)
    return proxy
end

function Account:deposit(v)
    account[self].balance = self:getBalance() + v
end

function Account:withdraw(v)
    account[self].balance = self:getBalance() - v
end

function Account:getBalance()
    return account[self].balance or 0
end

proxy = Account:new({
    balance = 200
})
print(proxy:getBalance())
proxy:deposit(100)
print(proxy:getBalance())
proxy:withdraw(300)
print(proxy:getBalance())
posted @ 2023-08-17 19:32  小能日记  阅读(19)  评论(0编辑  收藏  举报