Lua幾個例子

lua-5.1.4.tar.gz裏test目錄下有一些測試用例,我看了一遍,令人印象深刻有:
string庫的模式匹配,簡化的正則,夠用不復雜。
協程和閉包,有時可簡化編程,但有時也不好理解。
table的__index和__newindex,可用來模擬面向對象,規則雖然簡單,但用好真不那麼容易。
《Lua程序設計》中也有一些例子,這些有趣的例子和工具都列在下面:

斐波那契(fibonacci)數列

這是正着算的

-- fibfor.lua
-- example of for with generator functions

function generatefib (n)
  return coroutine.wrap(function ()
    local a, b = 1, 1
    while a <= n do
      coroutine.yield(a)
      a, b = b, a+b
    end
  end)
end

function generatefib2 (n)
  local a, b = 1, 1
  return function()
    if a <= n then
      a, b = b, a+b
      return b-a
    end
  end
end

for i in generatefib(1000) do print(i) end
for i in generatefib2(1000) do print(i) end

這是倒着算的

-- fib.lua
-- fibonacci function with cache

-- very inefficient fibonacci function
function fib(n)
  N=N+1
  if n<2 then
    return n
  else
    return fib(n-1)+fib(n-2)
  end
end

-- a general-purpose value cache
function cache(f)
  local c={}
  return function (n)
    local y=c[n]
    if not y then
      y=f(n)
      c[n]=y
    end
    return y
  end
end

-- run and time it
function test(s,f,n)
  N=0
  local c=os.clock()
  local v=f(n)
  local t=os.clock()-c
  print(s,n,v,t,N)
end

local n=arg[1] or 24    -- for other values, do lua fib.lua XX
n=tonumber(n)
print("","n","value","time","evals")
test("plain",fib,n)
fib=cache(fib)
test("cached",fib,n)

遞歸層數過多之後堆棧很快就會溢出,通過cache有效減少遞歸調用,關鍵是依賴解釋執行這個特性,cache可以做成通用函數,在fib成爲一個新函數之後,在原fib函數定義中對fib的調用使用的是新的fib。

篩選法求質數

-- sieve.lua
-- the sieve of of Eratosthenes programmed with coroutines
-- typical usage: lua -e N=1000 sieve.lua | column

-- generate all the numbers from 2 to n
function gen (n)
  return coroutine.wrap(function ()
    for i=2,n do coroutine.yield(i) end
  end)
end

-- filter the numbers generated by `g', removing multiples of `p'
function filter (p, g)
  return coroutine.wrap(function ()
    while 1 do
      local n = g()
      if n == nil then return end
      if math.mod(n, p) ~= 0 then coroutine.yield(n) end
    end
  end)
end

N=N or 1000		-- from command line
x = gen(N)		-- generate primes up to N
while 1 do
  local n = x()		-- pick a number until done
  if n == nil then break end
  print(n)		-- must be a prime number
  x = filter(n, x)	-- now remove its multiples
end

每獲得一個新質數n之後,會創建一個協程filter n,它resume filter m(m是n之前的質數)來獲取下一個數,filter m resume filter s(s是m之前的質數),…,直到resume gen產生下一個整數,filter 2判斷這個數能否被2整除,如果能整除則繼續取下一個數,不能被整除則yield給filter 3,filter 3再判斷能否被3整除,如果能則resume filter 2獲取下一個數,如果不能則yield給filter 5,…,直到最後交給filter n,最後獲得這個數,不能被已獲得的質數整除,所以它是新的質數。
這段程序首先不太好理解,其次在運算過程中嵌套層數會越來越多,堆棧很快就會溢出,也就是作爲協程的例子看看就好了。

看不懂的

-- factorial.lua
-- function closures are powerful

-- traditional fixed-point operator from functional programming
Y = function (g)
  local a = function (f) return f(f) end
  return a(function (f)
    return g(function (x)
      local c=f(f)
      return c(x)
      end)
    end)
end

-- factorial without recursion
F = function (f)
  return function (n)
    if n == 0 then return 1
    else return n*f(n-1) end
    end
  end

factorial = Y(F)   -- factorial is the fixed point of F

-- now test it
function test(x)
  io.write(x,"! = ",factorial(x),"\n")
end

for n=0,16 do
  test(n)
end

雖然沒看懂,但我試着修改成了下面的代碼:

function F(f)
  return function (n)
    if n == 0 then return 1
    else return n*f(n-1) end
  end
end

factorial = F(function(n) return factorial(n) end)

print(factorial(4))

檢測未聲明便使用的全局變量的工具

-- strict.lua
-- checks uses of undeclared global variables
-- All global variables must be 'declared' through a regular assignment
-- (even assigning nil will do) in a main chunk before being used
-- anywhere or assigned to inside a function.

local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget

local mt = getmetatable(_G)
if mt == nil then
  mt = {}
  setmetatable(_G, mt)
end

mt.__declared = {}

local function what ()
  local d = getinfo(3, "S")
  return d and d.what or "C"
end

mt.__newindex = function (t, n, v)
  if not mt.__declared[n] then
    local w = what()
    if w ~= "main" and w ~= "C" then
      error("assign to undeclared variable '"..n.."'", 2)
    end
    mt.__declared[n] = true
  end
  rawset(t, n, v)
end
  
mt.__index = function (t, n)
  if not mt.__declared[n] and what() ~= "C" then
    error("variable '"..n.."' is not declared", 2)
  end
  return rawget(t, n)
end

測試如下:
lua -e ‘print(a);b=2’
lua -lstrict -e ‘print(a)’
lua -e ‘function f() b=2 end f()’
lua -lstrict -e ‘function f() b=2 end f()’

每次GC後運行一個函數

《Lua程序設計》第4版23.6析構器中有個有趣的例子,可以在每次GC後運行一個函數:

do
  local mt = {__gc = function(o)
    print("garbage collect")
    setmetatable({}, getmetable(o))
  end}
  setmetatable({}, mt)
end
collectgarbage()
collectgarbage()

這段代碼在5.3版本有效,在5.1版本不可用,參考手冊說:

當你爲一個對象設置元表時,若此刻這張元表中用一個以字符串 “__gc” 爲索引的域,那麼就標記了這個對象需要觸發終結器。 注意:如果你給對象設置了一個沒有 __gc 域的元表,之後纔給元表加上這個域, 那麼這個對象是沒有被標記成需要觸發終結器的。 然而,一旦對象被標記, 你還是可以自由的改變其元表中的 __gc 域的。
當一個被標記的對象成爲了垃圾後, 垃圾收集器並不會立刻回收它。 取而代之的是,Lua 會將其置入一個鏈表。 在收集完成後,Lua 將遍歷這個鏈表。 Lua 會檢查每個鏈表中的對象的 __gc 元方法:如果是一個函數,那麼就以對象爲唯一參數調用它; 否則直接忽略它。

數據序列化

《Lua程序設計》第4版15.2序列化一節挺有趣,將數據序列化成自解釋的文件,加載時直接調用loadfile函數即可,以下是序列化的實現,大部分情況下它是可用的,但如果一個表的鍵不是合法的Lua標識符,或錶帶有循環,就會有問題。

#!/usr/bin/env lua
local function serialize(o, prefix, header, tailer)
  if header then io.write(header) end
  local t = type(o)
  --from lua 5.3.3
  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')
    local newprefix = prefix..'\t'
    for k,v in pairs(o) do
      if type(k) == 'number' then
        io.write(newprefix, '[', k, '] = ')
      else
        io.write(newprefix, k, ' = ')
      end
      serialize(v, newprefix)
      io.write(',\n')
    end
    io.write(prefix, '}')
  else
    error('cannot serialize a '..t)
  end
  if tailer then io.write(tailer) end
end

local function pack(o, header)
  serialize(o, '', header, '\n')
end

t={v=1,[2]={y1=3,y2={z1=4,z2=5}}}
t[1]='hello world'
y=1/3
pack(y, 'y = ')
pack(t, 't = ')

繪製娥眉月

《Lua程序設計》第4版9.4函數式編程中有一個繪製娥眉月的例子:

#! /bin/env lua
--將繪圖區域(-1,-1)--(1,1)映射到區域(1,1)--(M,N),
--生成pbm格式位圖.
--遍歷(1,1)--(M,N)之間的所有像素點, 找到其在
--繪圖區域(-1,-1)--(1,1)的對應座標, 通過r判斷其亮暗.
local function plot(r, M, N)
	io.output('crescent.pbm')
	--pbm固定格式
	io.write('P1\n', M, ' ', N, '\n')
	for j=1,N do
		--因爲pbm文件中行數往下增長,區域(1,1)--(M,N)
		--中行數往上增長,所以取個相反數
		local y=1-(j-1)*2/N
		for i=1,M do
			local x=(i-1)*2/M-1
			io.write(r(x,y) and '0' or '1')
		end
		io.write('\n')
	end
end
--返回判斷座標是不是在(cx,cy),r的圓內的函數
local function disk(cx, cy, r)
	return function(x, y)
		return (x-cx)^2 + (y-cy)^2 < r^2
	end
end
--返回判斷(x,y)是否在集合s1內但不在s2內的函數
local function difference(s1, s2)
	return function(x, y)
		return s1(x, y) and not s2(x, y)
	end
end

plot(difference(disk(0,0,1), disk(0.3,0,1)), 600, 400)

將生成的文件在jinaconvert(這個網站可以將您的圖片轉換成多種圖片格式)轉成png格式如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章