前面已经初步认识了table,这次对table进行深入学习。

table类型实现了“关联数组(associative)”,它是一种具有特殊索引方式的数组,

不仅可以用整数来索引它,还可以使用字符串或其他类型的值(除了nil)来索引它。

此外,table没有固定的大小,可以动态地添加任意数量的元素到一个table中。

table是Lua中主要的(事实上也是仅有的)数据结构机制,具有强大的功能。

基于table,可以以一种简单、统一和高效的方式来表示普通数组、符号表(symbol table)、集合、记录、队列和其他数据结构。Lua也是通过table来表示模块(module)、包(package)和对象(object)的。当输入io.read的时候,

其含义是 ” io模块中的read函数 “。对Lua而言,这表示“ 使用字符串“ read " 

作为key来索引 table io”。

1、table的构造式(constructor expression)

tab1 = {78, 45,8}
tab2 = {x = 78, y = 45,z = 99}

tab1是用构造式初始化一个数组,tab2使用了Lua提供的一种特殊的语法用于

初始化记录风格的table,也可以将两种风格混合使用。

注意:

local  a = {}
x = "y"
a[x] = 10
print("a[x] = ", a[x])  --->10
print("a.x = ", a.x)    --->nil
print("a.y = ", a.y)    --->10


上面的代码中,a.x 和 a[x] 是有区别的:a.x 相当于 a["x"]

2、table的内存与长度操作

table永远是“匿名的(anonymous)”,一个持有table的变量与table自身之间

没有固定的关联性。当一个程序再也没有一个table的引用时,Lua的垃圾回收器(garbage collector)最终会删除该table,并复用它的内存。


a = {}
a["x"] = 10
b = a --->a与b引用了同一个table
print(b["x"]) --->10
b["x"] = 20
print(a["x"]) --->10
a = nil --->还有b在引用table
b = nil -->再也没有对table的引用了,gc会删除该table



和string的长度操作一样,用一个井号(#)就能得到table的长度:

-- 打印表 a 的所有元素
	for i = 1, #a do
		print(a[i])
	end



print(a[#a])      --->打印a中的最后一个值
a[#a] = nil--->删除a中的最后一个值</span>
a[#a + 1] = v     --->在尾部添加一个值</span>

注意:  

1). 再次提醒Lua中的下标是从1开始的,但是你也可以让元素的下标从任意值开始。

2).Lua 5.0不支持长度操作符,可以使用函数table.getn来获得类似的结果。建议使用最新的Lua,

现在更新到 5.2 了。

3).Lua 将nil作为界定数组结尾的标志。所以,当数组中有空隙时(Hole,即有nil),长度操作符

会认为这些nil 元素就是结尾标记。

4). Lua 5.1 新加的一个函数table.maxn 可以返回一个table的最大正索引。

3、table实现的数据结构

1).数组,前面已经接触到数组了:

-- 新建一个数组
a = {}
for i = 1, 1000 do 
a[i] = 0
end


也可以用多维数组来表示一个矩阵:

-- 初始化一个 N * M 的矩阵
matrix = {}
for i = 1, N do
matrix[i] = {}
    for j = 1, M do
	matrix[i][j] = 0
     end
end

2).链表:链表中每个节点具有两个字段:指向下一个节点的next和当前节点

保存的值value。


head = nil--->表头
head = { next = head, value = 90} --->在表头插入一个元素

list1 = { next = list1, value = 91}
head.next = list1

list2 = { next = list2, value = 92}
list1.next = list2

local x = head
while x do
	print(x.value)
	x = x.next
end




3). 队列与双向队列:实现队列的一种简单方法是使用table库的函数 insert 和 remove。

这两个函数可以在一个数组的任意位置插入或删除元素,并且根据操作要求移动后续元素。

一种更高效的实现是使用两个索引,分别用于首尾的两个元素:


queue = {}
function queue.new( )
	return { first = 0, last = -1}
end
function queue.pushFront(q, v)
	-- body
	local first = q.first - 1
	q.first = first
	q[first] = v
end

function queue.pushBack(q, v)
	-- body
	local last = q.last + 1
	q.last = last
	q[last] = v
end

function queue.popFirst(q)
	-- body
	local first = q.first
	if first > q.last then
		error("queue is empty")
	end
	local value = q[first]
	q[first] = nil
	q.first = first + 1
	return value
end

function queue.popLast(q)
	-- body
	local last = q.last
	if q.first > last then
		error("queue is empty")
	end
	local value = q[last]
	q[last] = nil
	q.last = last - 1
	return value
end
-- 打印队列元素
function queue.travel(q)
	-- body
	local first = q.first
	if first > q.last then
		error("queue is empty")
	end


	while first < q.last do
		local value = q[first]
		print(value)
		first = first + 1
	end
end



4). 图:图的实现比较复杂,这里参考《Programming in Lua》中的代码。

每个节点表示为一个table,这个table有两个字段:name和adj

(与此节点邻接的节点集合)。


-- 给定节点名称返回对应的节点
local function name2node (graph, name)
if not graph[name] then
	graph[name] = { name = name, adj = {} }
	end
	return graph[name]
end





-- 从文件中读取图数据
function readgraph ()
	local = graph = { }
	for line in io.lines() do
		local namefrom, nameto = string.match(line, "(%S+(%S+)")
		local from = name2node(graph, namefrom)
		local to = name2node(graph, nameto)
		from.adj[to] = true
	end
	return graph
end


-- 使用深度优先遍历


function findpath(curr, to, path, visited)
	path = path or { }
	visited = visited or { }
	if visited[curr] then
		return nil
		end
		visited[curr] = true
		path[#path + 1] = curr
		if curr == to then
		return path
	end
	-- 尝试所有的邻接节点
	for node in pairs(curr.adj) do
		local p = findpath(node, to, path, visited)
		if p then return p end
	end
	path[#path] = nil  ---从路径中删除节点
end




3、table内部的原理:元表(metatable)与元方法(metamethod)

元表与元方法是很重要的概念,后面会查找相关资料,做更详细的了解,

这里只是针对table中的元方法做简单介绍。

前面已经接触过,当访问table中不存在的key时就会返回一个nil,这是正常状况。

其实,table内部的访问机制是:当访问一个table中不存在的字段时,会促使

解释器去查找一个叫__index的元方法。如果没有这个元方法,那么访问的

结果就是nil,否则就由这个元方法来提供最终结果。对于添加元素,

也有一个类似的__newindex元方法。而这些方法是元表中提供的,所以

需要设置一个table的元表,用setmetatable函数即可。下面给出一个

带有默认值的table示例:


function setDefault (t, d)
	local mt = { __index = function () return d end }
	setmetatable(t, mt)
end


tab = { x = 10, y = 12 }
print( tab.x, tab.z )  ----->10   nil
setDefault( tab, 0 )
print( tab.x, tab.z )   ---->10     0


3、table库:由一些辅助函数构成,这些函数将table作为数组来操作。

1). 插入与删除:

table.insert ( t, pos, value ) -->将元素(value)插入到一个数组(t)的指定位置(pos),

如果没有指定位置参数,则默认将元素添加到数组的末尾。一般,用 t[#t + 1] 来

给一个列表添加元素。

table.remove( t ,pos ) 会删除(并返回)数组指定位置上的元素,并将该位置

之后的所有元素前移,以填补空隙。如果不指定位置参数,默认会删除数组的

最后一个元素。对于数据量比较小时,用这些方法比较合适,数据量大时开销

就太高,需要用更高级的算法来实现相关操作。

2). 排序:table.sort( t, cmp ), 可以对一个数组进行排序,还可以指定一个可选

的次序函数cmp。此参数是一个外部函数, 可以用来自定义sort函数的排序标准.

此函数应满足以下条件: 接受两个参数(依次为a, b), 并返回一个布尔型的值, 

当a应该排在b前面时, 返回true, 反之返回false.


例如, 当我们需要降序排序时, 可以这样写:

> sortFunc = function(a, b) return b < a end
> table.sort(tbl, sortFunc)
> print(table.concat(tbl, ", "))
------>gamma, delta, beta, alpha

用类似的原理还可以写出更加复杂的排序函数. 例如, 有一个table存有工会

三名成员的姓名及等级信息:

guild = {}

table.insert(guild, {
 name = "Cladhaire",
 class = "Rogue",
 level = 70,
})

table.insert(guild, {
 name = "Sagart",
 class = "Priest",
 level = 70,
})

table.insert(guild, {
 name = "Mallaithe",
 class = "Warlock",
 level = 40,
})

对这个table进行排序时, 应用以下的规则: 按等级升序排序, 在等级相同时,

 按姓名升序排序.

可以写出这样的排序函数:

function sortLevelNameAsc(a, b)
 if a.level == b.level then
  return a.name < b.name
 else
  return a.level < b.level
 end
end

3). 连接:table.concat(table, sep,  start, end)

concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中

指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的

分隔符(sep)隔开。除了table外, 其他的参数都不是必须的, 分隔符的

默认值是空字符, start的默认值是1, end的默认值是数组部分的总长.

sep, start, end这三个参数是顺序读入的, 所以虽然它们都不是必须参数,

 但如果要指定靠后的参数, 必须同时指定前面的参数.

> tbl = {"alpha", "beta", "gamma"}
> print(table.concat(tbl, ":"))
alpha:beta:gamma
> print(table.concat(tbl, nil, 1, 2))
alphabeta
> print(table.concat(tbl, "\n", 2, 3))
beta
gamma