《Lua程序设计第四版》 第一部分自做练习题答案

发布时间 2023-08-14 11:27:39作者: 小能日记

Lua程序设计第四版第一部分语言基础自做练习题答案,带⭐为重点。

1.1

输入负数,程序会死循环,修改如下

-- 定义一个计算阶乘的函数
function fact(n)
  if n <= 0 then
    return 1
  else
    return n * fact(n - 1)
  end
end

print("enter a number")
a = io.read("*n")
while a < 0 do
  print("number is negative, enter a number")
  a = io.read("*n")
end
print(fact(a))

1.2

-l

载入库,在lua解释器之外运行。

lua -l lib1

dofile

读取文件,在lua解释器之内运行。

print("use dofile")
dofile("1.1.lua")

1.4

end 为关键字,until?带?无关字符,nil为关键字,one-step中带-无关字符,这几个不是

1.5

false,因为type函数总是返回字符串。

1.6

--a = true
a = false
--a = 1
--a = "true"
print(a == true or a == false)

1.7

不是必须的,加括号更清晰。

1.8

print(arg[0])

2.0 ⭐

八皇后问题有92解。皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。

回溯法

N = 8         -- 棋盘大小
FuncCount = 0 --调用IsPlaceOK的次数

-- 检查第n行第c列皇后会不会被前面已有的n-1个皇后攻击
function IsPlaceOK(a, n, c)
  for i = 1, n - 1 do
    if a[i] == c or n - i == a[i] - c or n - i == c - a[i] then
      return false
    end
  end
  return true
end

-- 打印棋盘
function PrintSolution(a)
  for i = 1, N do
    for j = 1, N do
      io.write(a[i] == j and "x" or "-", " ")
    end
    io.write("\n")
  end
  io.write("\n")
end

-- 放置从n到N的皇后,递归
function AddQueen(a, n)
  if n > N then
    PrintSolution(a)
  else -- 放置第n个皇后
    for c = 1, N do
      ok = IsPlaceOK(a, n, c)
      FuncCount = FuncCount + 1
      if ok then
        a[n] = c
        AddQueen(a, n + 1)
      end
    end
  end
end

AddQueen({}, 1)
print(string.format("调用IsPlaceOK的次数%d", FuncCount))

2.1

修改打印棋盘函数,调用操作系统函数,首次打印时提前退出

os.exit()

2.2 ⭐

全排列问题,胡凡算法笔记第115页。需要一个额外的表来记录状态。

N = 8          -- 棋盘大小
HashTable = {} -- 记录状态
Count = 0      -- 记录数量
FuncCount = 0  --调用IsPlaceOK的次数
for i = 1, N do
  HashTable[i] = false
end

-- 打印棋盘
function PrintSolution(a)
  for i = 1, N do
    for j = 1, N do
      io.write(a[i] == j and "x" or "-", " ")
    end
    io.write("\n")
  end
  io.write("\n")
end

-- 检查排列
function IsPlaceOK(a)
  for i = 1, N do
    for j = i + 1, N do
      if a[i] == a[j] or j - i == a[i] - a[j] or j - i == a[j] - a[i] then
        return
      end
    end
  end
  PrintSolution(a)
  Count = Count + 1
end

-- 生成皇后
function AddQueen(a, n)
  if n > N then
    IsPlaceOK(a)
    FuncCount = FuncCount + 1
  end
  for i = 1, N do
    if HashTable[i] == false then
      a[n] = i
      HashTable[i] = true
      AddQueen(a, n + 1)
      HashTable[i] = false
    end
  end
end

AddQueen({}, 1)
print(string.format("共%d种方法", Count))
print(string.format("调用IsPlaceOK的次数%d", FuncCount))

2.0调用IsPlaceOK的次数15720,2.2调用IsPlaceOK的次数40320

3.1

print(0e12)
-- print(.e12)
-- print(0.0e)
print(0x12)
-- print(0xABFG)
print(0xA)
-- print(FFFF)
print(0xFFFFFFFF)
-- print(0x)
print(0x1P10)
print(0.1e1)
print(0x0.1p0)
print(0x0.Fp0)
print(0x0.1p1)
print(0x0.Fp1)

3.2

print(0xFFFFFFFFFFFFFFFF) -- -1
print(0x8000000000000000) -- 最小的负整数
print(0x7FFFFFFFFFFFFFFF)
print(0x7FFFFFFFFFFFFFFF * 2) -- 回环,对最高位取舍,等价于
print(0xFFFFFFFFFFFFFFFE)
print(0x8000000000000000 * 2) -- 回环,对最高位取舍,等价于
print(0x0000000000000000)
-- math.maxinteger除首位外全1,相乘后得到除最后一位和首部外,中间全0,超过符号位以外的部分被抛弃,其余全为0,最后只剩最后一位的1,所以结果为1。
-- math.mininteger除首位外全0,超出符号位以外的数值均被抛弃,其余全为0,所以结果为0。

--[[
二进制乘法要求满二进一原则
"_"符号为舍弃高位

求解:0x7FFFFFFFFFFFFFFF * 2
     01111111  <=  0x7F
*        0010  <=  0x2
=============
     00000000
    01111111
=============
    _11111110  =>  0xFE

求解:0x8000000000000000 * 2
     10000000  <=  0x80
*        0010  <=  0x2
=============
     00000000
    10000000
=============
    _00000000  =>  0x00

求解:math.maxinteger * math.maxinteger
     0111  
*    0111
=============
     0111
    0111
   0111
  0111     
=============
    _0001  =>  1D

范例
     1001
*    1101
=============
     1001
    0000
   1001
+ 1001
=============
  1110101
]]

3.3

-10     2
-9      0
-8      1
-7      2
-6      0
-5      1
-4      2
-3      0
-2      1
-1      2
0       0
1       1
2       2
3       0
4       1
5       2
6       0
7       1
8       2
9       0
10      1

3.4

print(2 ^ 3 ^ 4)
print(2 ^ (3 ^ 4))
print((2 ^ 3) ^ 4)
print(2 ^ -3 ^ 4) -- 幂运算优先级大于"-"
print(2 ^ (-3) ^ 4)
print(2 ^ -(3 ^ 4))
-- 2.4178516392293e+024
-- 2.4178516392293e+024
-- 4096.0
-- 4.1359030627651e-025
-- 2.4178516392293e+024
-- 4.1359030627651e-025

3.5

不懂

print(12.7 == 25.4/2)
print(12.7 == 406.4 / 2^5)
print(5.5 == 55/10)
print(5.5 == 11/2)
print(5.5 == 22/4)
print(5.5 == 176/2^5)

3.6 ⭐

function CalCloneVolume(r, h)
    return math.pi * r ^ 2 * h / 3
end

print(CalCloneVolume(2, 3) == 12.566370614359) -- false 的原因

answer-1

You're multiplying something by pi, so the result is an irrational number which has a non-repeating fractional portion. .566370614359 is only the first 12 decimal places of a sequence that continues forever.

Internally, Lua uses a 64-bit floating point representation (on most platforms), so it only contains an approximation of that fraction, but it has a lot more precision than 12 decimal places. You'd have to understand the IEEE-754 floating point pretty intimately to predict the exact value for an equality comparison like that.

I can get true on my machine if I go out to at least 15 decimal places, but you shouldn't use floating point numbers that way.


I suspect you got 12.566370614359 by just printing the output of CalCloneVolume(2, 3). You're getting 12 decimal places there only because that's the default inside some routine in Lua that formats floating point numbers for display. You could show more decimal places like this:print(string.format("%.50g", CalCloneVolume(2, 3)))

answer-2

Generally it’s best not to compare floating point numbers for exactly equality unless you know they are storing integers. Integers (within a certain range) are stored precisely - so integer comparisons are ok.

Other real numbers (with fractional parts) are stored in binary not in decimal, which means that it’s hard to know what the exact number is - and some numbers can’t represented exactly. Irrational numbers are one example for both decimal and binary, but there are plenty of others - and they are not the same for binary and decimal.

Additionally different machines can have different bits, e g. 64 bits are common with PC, but you might want to only use 32 bits on a embedded device. Some machines use 80 bits. Additionally what they use these bit for inside a floating point number vary.

Additionally you can get problems with certain operations - divide for instance is the normal trouble maker. I notice you are dividing by 3. If you divide 1 by three you can’t represent this number exactly as with a finite number of digits on decimal or binary. (You can in ternary… base 3). In decimal it’s 0.333333333…forever and a 0.0101001010101010101… forever in binary but 0.1 in base 3. With floating point numbers you are only using a fixed number of bits.

As an example you might end up with three lots of 1/3 added together … which mathematically equals one, but in decimal representation looks like 0.99999999999 - something which confuses students today.

Comparing for higher then > or lower than < is fine. As of compare higher or equal >= or lower than equal <=.

If you need to compare for equality where you think you can get an approximate number … then you can check if it’s close to a number … for instance if x > 0.9 and x < 1.1

People get upset that some calculation ends up as 5.000000001 but floating point numbers are really cool as long as you understand their limitations.

3.7 ⭐

其他正态分布都可以用 0-1 正态分布 N(0,1) 生成。由概率论知, 若 \(Y_{1}, Y_{2}, \cdots, Y_{N}\) 是 n 个独立同分布的随机变量, 当 n 足够大时 \(X=Y_{1}+Y_{2}+\cdots+Y_{n}\) 就近似于一个正态分布.一般来说, 当 n>6 时, X 就是一个正态分布的很好的近似. X 的均值和方差分别为

\(\begin{array}{c} \mu_{x}=\mu_{1}+\mu_{2}+\cdots+\mu_{n}=n \mu \\ \sigma_{x}^{2}=\sigma_{1}^{2}+\sigma_{2}^{2}+\cdots+\sigma_{n}^{2}=n \sigma^{2} \end{array}\)

\(\begin{array}{c} X=Y_{1}+Y_{2}+\cdots+Y_{n} \\ z=\frac{\sum_{i=1}^{n} y_{i}-n \mu}{\sqrt{n \sigma^{2}}}, \quad z \in N(0,1) \end{array}\)

\(Y_{i}\) 为 0-1 均匀分布 U(0,1) , 已知 \(\mu=\frac{1}{2}, \sigma=\frac{1}{2 \sqrt{3}}\) , 代入

\(z=\frac{\sum_{i=1}^{n} y_{i}-n / 2}{\sqrt{n / 12}}\)

取 n=12 , 得 \(z=\sum_{i=1}^{12} y_{i}-6\)

给定数学期望和标准差, 计算 \(x=\mu+\sigma Z\) 便可得到服从 \(N\left(\mu, \sigma^{2}\right)\) 正态分布的随机数 x

-- 正态分布随机数发生器
function NormalDistribution(mean, std) -- 给出范围
    local z = 0
    for i = 1, 12, 1 do
        z = z + math.random()
    end
    z = z - 6
    return z * std + mean
end

local count = 0
local threshold = 2.576 -- 1.645
for i = 1, 10 ^ 5, 1 do
    x = NormalDistribution(0, 1)
    if x < threshold and x > -threshold then
        count = count + 1
    end
end
print(count)
print(string.format("%.6f", count / 10 ^ 5))

4.1

xml = [==[
<![CDATA]
Hello World
]]>]==]
print(xml)
xml2 = "\z
<![CDATA]\n\z
Hello World\n\z
]]>"
print(xml2)
xml3 = "\060\033\091CDATA\091\010Hello\032world\010\093\093\062"
print(xml3)

4.2

考虑可读性和字符串最大长度,可以使用 [==[ ... ]==]\z 的形式。

如果字符串不是很多,可以用 \ddd\xhh\u{h..h} 来表示字符串。

4.3

function insert(a, index, b)
    return string.sub(a, 1, index - 1) .. b .. string.sub(a, index, -1)
end

print(insert("hello world", 1, "start: "))
print(insert("hello world", 7, "small "))

4.4

function insert(a, index, b)
    index = utf8.offset(a, index)
    return a:sub(1, index - 1) .. b .. a:sub(index, -1)
end

print(insert("résumé", 7, "!"))
print(insert("résumé", 6, "!"))

4.5

function remove(s, i, k)
    return s:sub(1, i - 1) .. s:sub(i + k, -1)
end

print(remove("hello world", 7, 4))

4.6

function remove(s, i, k)
    i = utf8.offset(s, i)
    k = utf8.offset(s, i + k)
    return s:sub(1, i - 1) .. s:sub(k, -1)
end

print(remove("résumé", 2, 2))

4.7

function ispali(s)
    for i = 1, #s // 2 do
        if s:byte(i) ~= s:byte(-i) then
            return false
        end
    end
    return true
end

print(ispali("banana"))
print(ispali("step on no pets"))

5.1

sunday = "monday";
monday = "sunday"
t = {
    sunday = "monday",
    [sunday] = monday
}
print(t.sunday, t[sunday], t[t.sunday])
-- monday  sunday  sunday

5.2

a = {}
a.a = a
print(a)
print(a.a)
print(a.a.a)
print(a.a.a.a)
print(a == a.a)
print(a.a.a == a.a.a.a)
--[[
table: 00000000009e0f50
table: 00000000009e0f50
table: 00000000009e0f50
table: 00000000009e0f50
true
true
]]

a.a.a.a = 3 -- => {}.a = 3

print(a)
print(a.a)
-- print(a.a.a) -- attempt to index a number value

5.3

a = {
    ["\007"] = "\\a",
    ["\008"] = "\\b",
    ["\009"] = "\\t",
    ["\010"] = "\\n",
    ["\011"] = "\\v",
    ["\012"] = "\\f",
    ["\013"] = "\\r",
    ["\\"] = "\\\\",
    ["\""] = "\\\"",
    ["\'"] = "\\'"
}

for k, v in pairs(a) do
    print(k, v)
end

5.4

function calfunc(t, x)
    local sum = 0
    for i, v in ipairs(t) do
        sum = sum + v * x ^ (i - 1)
    end
    return sum
end

t = {1, 2, 1}
print(calfunc(t, 4))

5.5

function calfunc(t, x)
    local sum = 0
    local xb = x
    if t[1] then
        sum = t[1]
    end
    for i = 2, #t, 1 do
        sum = sum + t[i] * x
        x = x * xb
    end
    return sum
end

t = {1, 2, 1}
print(calfunc(t, 4))

5.6

function isValidSequence(t)
    for i = 1, #t, 1 do
        if t[i] == nil then
            return false
        end
    end
    return true
end

print(isValidSequence({1, 2, 3, 4, 5}))
print(isValidSequence({1, 2, 3, 4, 5, nil}))
print(isValidSequence({1, 2, 3, nil, 5}))
print(isValidSequence({nil, 2, 3, 4, 5}))
-- pairs\ipairs 会跳过nil键值对

5.7

function moveTable(a, b, i)
    table.move(b, i, #b, i + #a)
    table.move(a, 1, #a, i, b)
end

a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
b = {-1, -2, -3}

moveTable(a, b, 2)

for k, v in pairs(b) do
    print(k, v)
end

5.8

.. 多次拼接,每次创建新字符串,性能低

function concat(t)
    local s = ""
    for i, v in ipairs(t) do
        s = s .. v
    end
    return s
end

print(concat({"hello", " ", "world"}))
print(table.concat({"hello", " ", "world"}))

a = {}
for i = 1, 2 ^ 19, 1 do
    a[i] = 'a'
end

start = os.clock()
concat(a)
print(os.clock() - start)

start = os.clock()
table.concat(a)
print(os.clock() - start)
--[[
hello world
hello world
8.1589999999997
0.0070000000014261
]]

6.1

function showArray(list)
    for i = 1, #list, 1 do
        print(list[i])
    end
end

showArray({1, 2, 3, 4, 5})

6.2

function showArray(...)
    for i, v in ipairs({...}) do
        if i ~= 1 then
            print(v)
        end
    end
end

showArray(1, 2, 3, 4, 5)

6.3

function showArray(...)
    t = {...}
    for i, v in ipairs(t) do
        if i ~= #t then
            print(v)
        end
    end
end

showArray(1, 2, 3, 4, 5)

6.4

-- 排列问题
function randomArray(t, index, state)
    index = index or {}
    state = state or {}
    if #index == #t then
        for i = 1, #t, 1 do
            print(t[index[i]])
        end
        return index
    end
    repeat
        a = math.random(1, #t)
    until state[a] ~= true
    state[a] = true
    index[#index + 1] = a
    return randomArray(t, index, state) -- 尾调用消除
end

randomArray({1, 2, 3, 4, 5})

-- for i, v in ipairs(randomArray({1, 2, 3, 4})) do
--     print(i, v)
-- end

6.5

res = {}
tmp = {}
function showCombine(arr, x)
    choose(arr, 1, 1, x)
end
-- arr-数组 n-选择组合中第几个 i-从第几个索引开始取 x-需要取多少个元素
function choose(arr, n, i, x)
    if (n > x) then
        res[#res + 1] = {table.unpack(tmp)}
        return
    end
    for k = i, #arr do
        tmp[n] = arr[k]
        choose(arr, n + 1, k + 1, x)
    end
end

showCombine({1, 2, 3, 4, 5}, 3)

for i = 1, #res do
    for z = 1, #res[i] do
        io.write(res[i][z])
    end
    io.write("\n")
end

7.1

function sortTXT(from, to)
    local t = {}
    from = from or ""
    to = to or ""
    if from ~= "" then
        local f = assert(io.open(from, "r"))
        for line in f:lines() do
            t[#t + 1] = line
        end
        f:close()
    else
        io.input(io.stdin)
        for line in io.lines() do
            t[#t + 1] = line
            if line == "" then
                break
            end
        end
    end
    table.sort(t)
    if to ~= "" then
        f = assert(io.open(to, 'w'))
        for _, v in ipairs(t) do
            f:write(v, "\n")
        end
        f:close()
    else
        for _, v in ipairs(t) do
            io.write(v, "\n")
        end
    end
end

-- sortTXT()
sortTXT("./test.txt")
sortTXT("./test.txt", "./test2.txt")

7.2

function sortTXT(from, to)
    local t = {}
    from = from or ""
    to = to or ""
    if from ~= "" then
        local f = assert(io.open(from, "r"))
        for line in f:lines() do
            t[#t + 1] = line
        end
        f:close()
    else
        io.input(io.stdin)
        for line in io.lines() do
            t[#t + 1] = line
            if line == "" then
                break
            end
        end
    end
    table.sort(t)
    if to ~= "" then
        err = io.open(to, 'r')
        if err ~= nil then
            err:close()
            io.stdout:write("当前文件已存在,确定请输 1\n")
            ok = io.stdin:read("l")
            if ok ~= "1" then
                return
            end
        end
        f = assert(io.open(to, 'w'))
        for _, v in ipairs(t) do
            f:write(v, "\n")
        end
        f:close()
    else
        for _, v in ipairs(t) do
            io.write(v, "\n")
        end
    end
end

sortTXT("./test.txt", "./test2.txt")

7.3

io.input("./huge.txt")
start = os.clock()
for count = 1, math.huge do
    s = io.read(1)
    if s == nil then
        break
    end
    io.write(s)
end
io.write(os.clock() - start)
-- 3.839  26.559

-- start = os.clock()
-- for count = 1, math.huge do
--     s = io.read('l')
--     if s == nil then
--         break
--     end
--     io.write(s)
-- end
-- io.write(os.clock() - start)
-- 0.654 4.309

-- start = os.clock()
-- for count = 1, math.huge do
--     s = io.read(2 ^ 13)
--     if s == nil then
--         break
--     end
--     io.write(s)
-- end
-- io.write(os.clock() - start)
-- 0.579 4.122

-- start = os.clock()
-- s = io.read('a')
-- io.write(s)
-- io.write(os.clock() - start)
-- 0.5969999  4.201

7.4

function lastLine(file)
    local f = io.open(file, 'r')
    local current = f:seek()
    local size = f:seek("end")
    local tmp = ""
    f:seek("set", current)
    if size > 2 ^ 20 then
        f:seek("set", size // 2 + size // 4 + size // 8) -- 切到 7/8 进度
    end
    for line in f:lines() do
        tmp = line
    end
    io.write(tmp, "\n")
    f:close()
end

lastLine("./huge.txt")

7.5

function lastLine(file, n)
    local f = io.open(file, 'r')
    local current = f:seek()
    local size = f:seek("end")
    local tmp = {}
    f:seek("set", current)
    if size > 2 ^ 20 then
        f:seek("set", size // 2 + size // 4 + size // 8) -- 切到 7/8 进度
    end
    for line in f:lines() do
        tmp[#tmp + 1] = line
    end
    for i = 1, n, 1 do
        io.write(tmp[#tmp - n + i], "\n")
    end
    f:close()
end

lastLine("./huge.txt", 3)

8.2

while true do
end

for i = 1, math.huge do
end

repeat
until nil

::s1::
do
    goto s1
end

8.4

function room1()
    local move = io.read()
    if move == "south" then
        return room3()
    elseif move == "east" then
        return room2()
    else
        print("invalid move")
        return room1()
    end
end

function room2()
    local move = io.read()
    if move == "south" then
        return room4()
    elseif move == "west" then
        return room1()
    else
        print("invalid move")
        return room2()
    end
end

function room3()
    local move = io.read()
    if move == "north" then
        return room4()
    elseif move == "east" then
        return room1()
    else
        print("invalid move")
        return room3()
    end
end

function room4()
    print("Congratulations, you won!")
end

room1()

8.5

不同函数之间如果能使用goto跳转会导致代码可读性变差,不利于维护,其次,直接跳转到某个变量的作用域,却绕过了变量的声明将会发生无法预料的错误

goto

print(1919180)
-- goto bb
do
    local x = 1
    goto bb
    ::cc:: -- 不能跳转到局部变量作用域
    x = 3
    print(x)
    ::dd:: -- 可以跳转到这,局部变量作用域终止于声明变量的代码块中的最后一个有效语句
end
::bb::

do
    ::aa:: -- 可见性原则
    print("114514")
end
-- goto aa

-- 另外不能跳转到函数外