《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())