Lua语言学习之table探索

来源:互联网 发布:天刀好看少女捏脸数据 编辑:程序博客网 时间:2024/05/29 21:17


 前面已经初步认识了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] = 10print("a[x] = ", a[x])  --->10print("a.x = ", a.x)    --->nilprint("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"] = 10b = a --->a与b引用了同一个tableprint(b["x"]) --->10b["x"] = 20print(a["x"]) --->10a = nil --->还有b在引用tableb = nil -->再也没有对table的引用了,gc会删除该table

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

-- 打印表 a 的所有元素for i = 1, #a doprint(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] = 0end


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

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

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

保存的值value。

head = nil--->表头head = { next = head, value = 90} --->在表头插入一个元素list1 = { next = list1, value = 91}head.next = list1list2 = { next = list2, value = 92}list1.next = list2local x = headwhile x doprint(x.value)x = x.nextend

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

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

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

queue = {}function queue.new( )return { first = 0, last = -1}endfunction queue.pushFront(q, v)-- bodylocal first = q.first - 1q.first = firstq[first] = vendfunction queue.pushBack(q, v)-- bodylocal last = q.last + 1q.last = lastq[last] = vendfunction queue.popFirst(q)-- bodylocal first = q.firstif first > q.last thenerror("queue is empty")endlocal value = q[first]q[first] = nilq.first = first + 1return valueendfunction queue.popLast(q)-- bodylocal last = q.lastif q.first > last thenerror("queue is empty")endlocal value = q[last]q[last] = nilq.last = last - 1return valueend-- 打印队列元素function queue.travel(q)-- bodylocal first = q.firstif first > q.last thenerror("queue is empty")endwhile first < q.last dolocal value = q[first]print(value)first = first + 1endend


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

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

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

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

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


-- 使用深度优先遍历

function findpath(curr, to, path, visited)path = path or { }visited = visited or { }if visited[curr] thenreturn nilendvisited[curr] = truepath[#path + 1] = currif curr == to thenreturn pathend-- 尝试所有的邻接节点for node in pairs(curr.adj) dolocal p = findpath(node, to, path, visited)if p then return p endendpath[#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   nilsetDefault( 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

更多方法,参见:http://www.cnblogs.com/whiteyun/archive/2009/08/10/1543139.html



   

0 0