《Lua程序设计第四版》 第二部分9~13章自做练习题答案
Lua程序设计第四版第二部分编程实操自做练习题答案,带⭐为重点。
9.1
请编写一个函数integral,该函数以一个函数f为参数并返回其积分的近似值
使用右矩阵法近似积分值
function integral(f)
return function(a, b)
local sum = 0
for i = 1, 10000, 1 do
sum = sum + f(a + (b - a) * i / 10000)
end
return sum * (b - a) / 10000
end
end
function x3(x)
return 2 * x + 3 * x ^ 3
end
jf = integral(x3)
print(jf(0, 10)) -- 7601.510075 近似 7600
9.2
如下代码段将输出什么结果
function F(x)
return {
set = function(y)
x = y
end,
get = function()
return x
end
}
end
o1 = F(10)
o2 = F(20)
print(o1.get(), o2.get())
o2.set(100)
o1.set(300)
print(o1.get(), o2.get())
-- 10 20
-- 300 100
9.3 ⭐
编写练习5.4的柯里化版本
柯里化(Currying)是把接受多个参数
的函数变换成接受一个单一参数
(最初函数的第一个参数)的函数
,并且返回接受余下的参数且返回结果
的新函数的技术。
function newpoly(t)
return function(x)
local sum = 0
for i, v in ipairs(t) do
sum = sum + v * x ^ (i - 1)
end
return sum
end
end
t = newpoly({3, 0, 1})
print(t(0))
print(t(5))
print(t(10))
9.4
使用几何区域系统的例子,绘制一个北半球所能看到的峨眉月
编写一个被其他函数B包含的函数A时,被包含的函数A可以访问包含其的函数B的所有局部变量,这种特性被称为词法定界
-- 利用高阶函数和词法定界,定义一个指定圆心和半径创建圆盘的工厂 --
function disk(cx, cy, r)
return function(x, y)
return (x - cx) ^ 2 + (y - cy) ^ 2 <= r ^ 2
end
end
-- 创建一个指定边界的轴对称图形
function rect(left, right, top, bottom)
return function(x, y)
return left <= x and x <= right and bottom <= y and y <= top
end
end
-- 创建任何区域的补集
function complement(r)
return function(x, y)
return not r(x, y)
end
end
-- 创建任何区域的并集
function union(r1, r2)
return function(x, y)
return r1(x, y) or r2(x, y)
end
end
-- 交集
function intersection(r1, r2)
return function(x, y)
return r1(x, y) and r2(x, y)
end
end
-- 差集
function difference(r1, r2)
return function(x, y)
return r1(x, y) and not r2(x, y)
end
end
-- 按指定增量平移区域
function translate(r, dx, dy)
return function(x, y)
return r(x - dx, y - dy)
end
end
function 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
circle = disk(0, 0, 0.5)
circle2 = translate(circle, -0.3, 0.2)
shape = difference(circle, circle2)
plot(shape, 512, 512, "test.pbm")
9.5 ⭐
在几何区域系统的例子中,添加一个函数来实现将指定的区域旋转指定的角度
![image-20230814141934874](https://xiaonenglife.oss-cn-hangzhou.aliyuncs.com/static/pic/2023/08/20230814141957_image-20230814141934874.png)
-- 创建一个指定边界的轴对称图形
function rect(left, right, top, bottom)
return function(x, y)
return left <= x and x <= right and bottom <= y and y <= top
end
end
-- 创建任何区域的并集
function union(r1, r2)
return function(x, y)
return r1(x, y) or r2(x, y)
end
end
-- 图形根据坐标轴原点顺时针旋转d弧度
function rotate(r, d)
return function(x, y)
return r(-math.cos(d) * x + y * math.sin(d), -math.sin(d) * x - math.cos(d) * y)
end
end
function 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
shape1 = rect(-0.5, 0.5, 0.5, -0.5)
shape2 = rect(-0.25, 0.25, 0.75, -0.25)
shape1 = rotate(shape, math.pi * 0.1) -- s1所处的坐标系平面被旋转了,s2的并没有
shape = union(shape1, shape2)
plot(shape, 512, 512, "test.pbm")
![image-20230814150430157](https://xiaonenglife.oss-cn-hangzhou.aliyuncs.com/static/pic/2023/08/20230815040414_image-20230814150430157.png)
10.1
请编写一个函数split,该函数接收两个参数,第1个参数是字符串,第2个参数是分隔符模式,函数的返回值是分隔符分割后的原始字符串中每一部分的序列
function split(s, sep)
t = {}
-- tmpindex = 0
-- index = 1
-- index = string.find(s, sep, index, true)
-- while index do
-- table.insert(t, string.sub(s, tmpindex + 1, index))
-- tmpindex = index
-- index = string.find(s, sep, index + 1, true)
-- end
-- if index ~= #s then
-- table.insert(t, string.sub(s, tmpindex + 1, #s))
-- end
for w in string.gmatch(s, "[^" .. sep .. "]+") do
t[#t + 1] = w
end
return t
end
t = split("a whole new world", " ")
-- t = split("", " ")
for k, v in pairs(t) do
print(k, v)
end
10.2 ⭐
模式 [^%d%u] 与 [%D%U] 是否等价
不一样
[^%d%u] 是 (数字∪大写字母)的补集 = 排除数字和大写字母
> string.find("123ABCA","[^%d%u]")
nil
> string.find("123ABCa","[^%d%u]")
7 7
[%D%U] 是 数字的补集∪大写字母的补集 = 全集
> string.find("123ABC","[%D%U]")
1 1
使用第一种是正确的
10.3
请编写一个函数transliterate,该函数接收两个参数,第1个参数是字符串,第2个参数是一个表。函数transliterate根据第2个参数中的表使用一个字符替换字符串中的字符。如果表中将a映射为b,那么该函数则将所有a替换为b。如果表中将a映射为false,那么该函数则把结果中的所有a移除
function transliterate(s, t)
return string.gsub(s, ".", function(w)
if t[w] == false then
return ""
else
return t[w]
end
end)
end
s = "apple"
s = transliterate(s, {
a = "b",
p = false,
l = "i",
e = "g"
})
print(s)
10.4 ⭐
我们定义了一个trim函数。
- 构造一个可能会导致trim耗费O(n^2)时间复杂度的字符串
- 重写这个函数使得其时间复杂度为O(n)
function trim1(s)
return string.gsub(s, "^%s*(.-)%s*$", "%1")
end
-- "双指针法实现"
function trim2(s)
local top = string.find(s, "%S")
local tail = #s
for i = 1, #s, 1 do
if string.find(string.sub(s, -i, -i), "%s") then
tail = #s - i
else
break
end
end
return string.sub(s, top, tail)
end
s = string.rep(" ", 2 ^ 13)
s = (" " .. "a" .. s .. "a" .. " ")
start = os.clock()
t = trim2(s)
print(t:len() == 2 ^ 13 + 2)
print(os.clock() - start)
10.5
请使用转义序列\x编写一个函数,将一个二进制字符串格式化为Lua语言中的字符串常量
-- 二进制字符串格式化为Lua语言中的字符串常量
function escape(s)
local pattern = string.rep("[01]", 8)
s = string.gsub(s, pattern, function(bits)
return string.format("\\x%02X", tonumber(bits, 2))
end)
return s
end
print(escape( "0100100001100001011100000111000001111001001000000110010101110110011001010111001001111001011001000110000101111001"))
-- \x48\x61\x70\x70\x79\x20\x65\x76\x65\x72\x79\x64\x61\x79
10.6
请为UTF-8字符串重写10.3
function transliterate(s, t)
return string.gsub(s, utf8.charpattern, function(w)
if t[w] == false then
return ""
else
return t[w]
end
end)
end
s = "天气真好"
s = transliterate(s, {
["天"] = "大",
["气"] = false,
["真"] = "爷"
})
print(s)
10.7
请编写一个函数,该函数用于逆转一个UTF-8字符串
function reverseUTF8(s)
t = {}
for w in string.gmatch(s, utf8.charpattern) do
table.insert(t, 1, w)
end
return table.concat(t)
end
print(reverseUTF8("天气真好"))
ANTI SPIDER BOT -- www.cnblogs.com/linxiaoxu
11.1
请改写该程序,使它忽略长度小于4个字母的单词
local counter = {}
io.input(arg[2] or "test.txt")
for line in io.lines() do
-- 不能放在这,相当于无限执行gmatch死循环,gmatch只执行一次获取迭代器
for word in string.gmatch(line, "%w+") do
if #word < 4 then
goto a
end
counter[word] = (counter[word] or 0) + 1
::a:: -- 跳过当前计数
end
end
local words = {}
for k, _ in pairs(counter) do
words[#words + 1] = k
end
table.sort(words, function(w1, w2)
return counter[w1] > counter[w2] or counter[w1] == counter[w2] and w1 < w2
end)
local n = math.min(tonumber(arg[1]) or math.huge, #words)
for i = 1, n, 1 do
print(i, words[i], counter[words[i]])
end
11.2 ⭐
该程序还能从一个文本文件中读取要忽略的单词列表
local counter = {}
local ignore = {}
io.input(arg[2] or "test.txt")
f = io.open(arg[3] or "ignore.txt", 'r')
for line in f:lines() do
ignore[string.match(line, "%w+")] = true
end
for line in io.lines() do
-- 不能放在这,相当于无限执行gmatch死循环,gmatch只执行一次获取迭代器
for word in string.gmatch(line, "%w+") do
if #word < 4 or ignore[word] then
goto a
end
counter[word] = (counter[word] or 0) + 1
::a:: -- 跳过当前计数
end
end
local words = {}
for k, _ in pairs(counter) do
words[#words + 1] = k
end
table.sort(words, function(w1, w2)
return counter[w1] > counter[w2] or counter[w1] == counter[w2] and w1 < w2
end)
local n = math.min(tonumber(arg[1]) or math.huge, #words)
for i = 1, n, 1 do
print(i, words[i], counter[words[i]])
end
12.1
该函数返回指定日期和时间后恰好一个月的日期和时间(按数字形式表示)
function oneMonthLater(t)
t.month = t.month + 1
return os.time(t)
end
print(oneMonthLater(os.date("*t")))
12.2
该函数返回指定日期是星期几(用整数表示,1表示星期天
function wday(t)
return t.wday
end
print(wday(os.date("*t")))
12.3
该函数的参数为一个日期和时间(数值),返回当天中已经经过的秒数
function todayPassSeconds(t)
t = os.date("*t", t)
return t.hour * 3600 + t.min * 60 + t.sec
end
print(todayPassSeconds(os.time()))
12.4
该函数的参数为年,返回该年中第一个星期五是第几天
function findFirstFriday(y)
t = os.date("*t")
t.year = y
t.month = 1
for i = 1, 7, 1 do
t.day = i
t = os.date("*t", os.time(t))
if t.yday == 6 then
return t.yday
end
end
end
print(findFirstFriday(2023))
12.5
该函数用于计算两个指定日期之间相差的天数
function timeDiffDays(x, y)
seconds = os.time(x) - os.time(y)
return seconds // (24 * 3600)
end
t = os.date("*t")
t.year = 2012
print(timeDiffDays(os.date("*t"), t))
12.6
该函数用于计算两个指定日期之间相差的月份
function timeDiffMonth(x, y)
year = x.year - y.year
month = x.month - y.month
return math.abs(year) * 12 + math.abs(month)
end
t = os.date("*t")
t.year = 2023
t.month = 7
print(timeDiffMonth(os.date("*t"), t))
12.7
向指定日期添加一个月再添加一天得到的结果,是否与先添加一天再添加一个月得到的结果相同
buff = {
year = 2023,
month = 6,
day = 30
}
t = os.date("*t", os.time(buff))
t.day = t.day + 1
t.month = t.month + 1
t2 = os.date("*t", os.time(buff))
t2.month = t2.month + 1
t2.day = t2.day + 1
print(os.difftime(os.time(t), os.time(t2)))
t = os.date("*t", os.time(buff))
t.day = t.day + 1
t = os.date("*t", os.time(t))
t.month = t.month + 1
t2 = os.date("*t", os.time(buff))
t2.month = t2.month + 1
t2 = os.date("*t", os.time(t2))
t2.day = t2.day + 1
print(os.difftime(os.time(t), os.time(t2)))
[[
0.0
86400.0 第二种方法会产生不同结果
]]
12.8
该函数用于输出操作系统的时区
时区划分是规定将全球划分为24个时区,东、西各12个时区。1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东112区,西112区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。
function timeZone()
local now = os.time()
local glnz = os.time(os.date("!*t")) -- 格林尼治时间
return (now - glnz) // 3600
end
print(timeZone())
-- 8 东8区
13.1 ⭐
该函数用于进行无符号整型数的取模运算
将无符号整数最低位提取充当b(0或1),其余部分为a,a必为偶数。a再拆分为 2 * a/2,2为j,a/2为k。
--设条件 d<= 2^64 -1 , c <= 2^63-1
function unsignedMod(d, c)
local even = d & -2
local odd = d & 1
local res1 = ((2 % c) * ((even >> 1) % c)) % c -- 等价于 even % c
local res2 = (res1 + (odd % c)) % c
return res2
end
print(unsignedMod(-101, 4294967296) == 4294967195)
print(unsignedMod(-1, math.maxinteger) == 1)
print(unsignedMod(-1111111, 114514) == 59155)
13.2
实现计算Lua语言中整型数所占位数的不同方法
-- 忽略符号位,计算剩下的63位中共占用几位
function countBits1(d)
local count = 0
while d ~= 0 do
d = d // 2
count = count + 1
end
return count
end
function countBits2(d)
local count = 0
d = d & math.maxinteger
while d ~= 0 do
d = d >> 1
count = count + 1
end
return count
end
print(countBits1(math.maxinteger) == 63)
print(countBits1(10) == 4)
print(countBits1(1) == 1)
print(countBits1(0) == 0)
print(countBits2(math.maxinteger) == 63)
print(countBits2(10) == 4)
print(countBits2(1) == 1)
print(countBits2(0) == 0)
13.3
如何判断一个指定整数是不是2的整数次幂
-- 忽略符号位,计算剩下的63位中共占用几位
function isPowerOf2(d)
if d <= 0 then
return false
end
while d & 1 == 0 do
d = d >> 1
end
d = d >> 1
if d > 0 then
return false
else
return true
end
end
print(isPowerOf2(1))
print(isPowerOf2(2))
print(isPowerOf2(3))
print(isPowerOf2(4))
print(isPowerOf2(5))
print(isPowerOf2(6))
print(isPowerOf2(7))
print(isPowerOf2(8))
13.4
该函数用于计算指定整数的汉明权重(数二进制表示的1的个数)
function hw(d)
local count = 0
while d ~= 0 do
if d & 1 == 1 then
count = count + 1
end
d = d >> 1
end
return count
end
print(hw(1)) -- 1
print(hw(2)) -- 1
print(hw(3)) -- 2
print(hw(4)) -- 1
print(hw(5)) -- 2
print(hw(6)) -- 2
print(hw(7)) -- 3
print(hw(8)) -- 1
print(hw(-1)) -- FFFF 64
print(hw(-2)) -- FFFE 63
print(hw(-3))
print(hw(-4))
print(hw(-5))
print(hw(-6))
print(hw(-7))
print(hw(-8))
13.5
该函数用于判断注定整数的二进制表示是否为回文数
function isPalindrome(d)
local t = {}
while d ~= 0 do
table.insert(t, d & 1)
d = d >> 1
end
for i = 1, 32, 1 do
if t[i] ~= t[64 - i + 1] then
return false
end
end
return true
end
print(isPalindrome(1))
print(isPalindrome(0))
print(isPalindrome(-1))
print(isPalindrome(math.mininteger + 1))
13.6 ⭐
请在Lua语言实现一个比特数组
- newBiteArrary(n) 创建一个具有n个比特的数组
- setBit(a, n, v) 将布尔值v赋值给数组a的第n位
- testBit(a, n) 将第n位的值作为布尔值返回
function newBiteArrary(n)
local t = {}
local count = (n - 1) // 64 + 1
for i = 1, n, 1 do
table.insert(t, 0)
end
return t
end
function setBit(a, n, v)
local count = (n - 1) // 64 + 1
local n = (n - 1) % 64 + 1
local tmp1 = math.mininteger >> (n - 1) -- 000001000000
local tmp2 = ~tmp1 -- 1111101111111
if v then
a[count] = a[count] | tmp1
else
a[count] = a[count] & tmp2
end
end
function testBit(a, n)
local count = (n - 1) // 64 + 1
local n = (n - 1) % 64 + 1
local tmp1 = math.mininteger >> (n - 1) -- 000001000000
if tmp1 & a[count] == 0 then
return false
else
return true
end
end
a = newBiteArrary(300)
setBit(a, 1, true)
setBit(a, 100, true)
setBit(a, 105, true)
setBit(a, 105, false)
setBit(a, 300, true)
print(testBit(a, 1))
print(testBit(a, 100))
print(testBit(a, 105))
print(testBit(a, 300))
13.7 ⭐
假设有一个以一系列记录组成的二进制文件,其中的每一个记录的格式为
struct Record{ int x; char[3] code; float value; };
请编写一个程序,该程序读取这个文件,然后输出value字段的总和
function saveRecords(t)
local f = io.open("records.txt", "w")
for _, v in ipairs(t) do
f:write(string.pack("j", v.x))
f:write(string.pack("z", v.code))
f:write(string.pack("n", v.value))
end
f:close()
end
function readCalcuteValue()
local f = io.open("records.txt", "rb")
local s = f:read('a')
f:close()
local i = 1
local total = 0
while i <= #s do
local x, code, value
x, i = string.unpack('j', s, i)
code, i = string.unpack('z', s, i)
value, i = string.unpack('n', s, i)
print(x, code, value)
total = total + value
end
return total
end
t = {{
x = 100000,
code = "abc",
value = 100
}, {
x = 200000,
code = "def",
value = 200
}, {
x = 300000,
code = "ghi",
value = 300
}, {
x = 400000,
code = "jkl",
value = 400
}}
saveRecords(t)
sum = readCalcuteValue()
print(sum)
[[
100000 abc 100.0
200000 def 200.0
300000 ghi 300.0
400000 jkl 400.0
1000.0
]]