借鉴《Lua程序设计(第四版)》第9章 – 闭包,9.4节 – 小试函数式编程。
目标是开发一个用来表示几何区域的系统,其中区域即为点的集合。
为了实现这样的一个系统,首先需要找到表示这些图形的合理数据结构。我们可以尝试使用面向对象的方案,利用继承来抽象某些图形;或者,也可以直接利用特征函数(characteristic or indicator function)来进行更高层次的抽象(集合
A
A
A 的特征函数
f
A
f_A
fA 是指当且仅当
x
x
x 属于
A
A
A 时
f
A
(
x
)
f_A(x)
fA(x) 成立)。鉴于一个几何区域就是点的集合,因此可以通过特征函数来表示一个区域,即可以提供一个点(作为参数)并根据点是否属于指定区域而返回真或假的函数来表示一个区域。
一、区域
举例来说,下面的函数表示一个以点 (1.0, 3.0) 为圆心,半径为 4.5 的圆形区域:
function disk1(x, y)
return (x - 1.0)^2 + (y - 3.0)^2 <= 4.5^2
end
利用高阶函数和词法定界,可以很容易地定义一个根据指定的圆心和半径创建圆盘的工厂:
function disk(cx, cy, r)
return function(x, y)
return (x - cx)^2 + (y - cy)^2 <= r^2
end
end
形如 disk(1.0, 3.0, 4.5) 的调用会创建一个与 disk1 等价的圆盘,如下所示:
print(disk(1, 1, 2)) --> function: 0x5581a2fb9bb0
print(disk(1, 1, 2)(1.5, 1.5)) --> true
print(disk(1, 1, 2)(2.5, 2.5)) --> false
下面的函数创建了一个指定边界的轴对称矩形:
function rect(left, right, bottom, up)
return function(x, y)
return left <= x and x <= right and bottom <= y and y <= up
end
end
按照类似的方式,可以定义函数以创建注入三角形或非轴对称矩形等其他基本图形。每一种图形都具有完全独立的实现,所需的仅仅是一个正确的特征函数。
二、区域的交、并、补、差
接下来考虑如何改变和组合区域。我们可以很容易地创建任何区域的补集(complement):
function complement(r) -- region
return function(x, y)
return not r(x, y)
end
end
print(complement(disk(1, 1, 2))(1.5, 1.5)) --> false
print(complement(disk(1, 1, 2))(2.5, 2.5)) --> true
同理,交、并、差也很简单:
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 diff(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
四、区域绘制
为了使一个区域可视化,我们可以遍历每个像素进行视口(viewport)测试;位于区域内的像素被绘制为黑色,位于区域外的像素被绘制为白色。为了简便,使用 PBM(可移植位图,portable bitmap)格式来绘制指定区域。
PBM 文件结构简单且高效。PBM 文件的文本形式以字符串 “P1” 开头,接下来的一行是图片的宽和高(以像素为单位),然后是对应每一个像素、由 1 和 0 组成的数字序列(黑为 1,白为 0,数字和数字之间由可选的空格分开,最后是 EOF。下面的函数 plot 创建了指定区域的 PBM 文件,并将虚拟绘图区域 (-1, 1], [-1, 1) 映射到视口区域 [1, M], [1, N]中。并绘制了一个南半球所能看到的峨眉月:
function plot(r, M, N)
io.write("P1\n", M, " ", N, "\n") -- 文件头
for i = 1, N do
local y = (N - i * 2) / N
for j = 1, M do
local x = (j * 2 - M) / M
io.write(r(x, y) and "1" or "0")
end
io.write("\n")
end
end
c1 = disk(0, 0, 1)
plot(diff(c1, translate(c1, 0.3, 0)), 500, 500)
程序中输出到了控制台,复制到文件,并改后缀名为 .pbm 后,打开可以看到下图:
五、Lua语法
值得注意的是,上边 plot 函数中有一个 io.write(r(x, y) and "1" or "0")
,这句和 C++ 的 ?: 表达式一样的效果,第一项为真,就是 “1”,否则就是 “0”。
例如:max = (x > y) and x or y
证明如下:
- 若 x > y,则 (x > y) 为 true,则 (x > y) and x 为 x,则 max = x or y = x;
- 若 x <= y,则 (x > y) 为 false,则 (x > y) and x 为 false,则 max = false or y = y。