lua中的数据结构真的很无敌。
用tables实现了数组,矩阵,稀疏矩阵,链表,栈,队列,双向队列,集合。
其中在操作队列的时候,lua手册中提供了一种处理大数据的方式,比标准库提供的标准函数要快。
再重述一下tables的功能:
tables就是一个对象,它里面什么都可以放(由于在lua中,函数也是一种数据类型,所以连函数都能放进去,感觉上像是一个类,但是操作型比类要强)。
它的下标可以是字符串,下标不要求连续,不要求从0开始(标准库中,对tables的操作函数,都是从下标1开始的,所以用到标准函数的时候,还是老实一点)
强调一下,lua中就变量的类型分类来讲,对整数,浮点,字符串没有太大的区别,一个变量,上一次还放着整型,下一次可能就放着字符串。
数组:可以存放不同类型的数据类型,每种类型组成的元素可以称它为tables中的一个域。所以一个数组里可以有多个域。
矩阵:可以通过像二维数组的方式,表里再放一个表来实现,也可以通过一维数组的方式存放一个矩阵。例如:
JZ={}
for i = 1,N do
for j = 1,M do
JZ[1 * M + j] = 0
end
end
稀疏矩阵:
单独提到稀疏矩阵,是想讲一下tables特性带来的便利。
假设有一个图,这个图上的每个点的连接数,需要用矩阵记录。有10000个点,每个点平均有5个连接。
正常情况下,需要一张10000 * 10000 的来做记录,在没有连接的地方写上0.
当采用tables记录这个矩阵的时候就不需要这么麻烦。将每一个连接的两个点的标号作为tables的下标。这样只需要10000*5 个记录就可以!至于没有连接的两个点,在这张表里,连记录都不会有。
链表和栈(这里手册上写的很模糊,我得再验证一下):
队列和双向队列:
在lua提供的标准函数中可以通过insert和remove来操作队列,但是这两个标准函数对于操作一个大的队列的时候会比较吃力,所以有了以下代码,效率更高
function PushLeft(list,value)
local first = list.first - 1
list[first] = value
list.first = first;
end
function PushRight(list,value)
local last = list.last + 1
list[last] = value
list.last = last
end
function PopLeft(list)
local first = list.first
if (list.first > list.last) then
print("this list is empty!")
return
end
local value = list[first]
list[first] = nil
list.first = first + 1
return value
end
function PopRight(list)
local last = list.last
if (list.first > list.last) then
print("this list is empty")
return
end
local value = list[last]
list[last] = nil
list.last = last - 1
return value
end
do
DL = {first = 0,last = -1}
for i = 1,10 do
PushLeft(DL,i)
end
for i = 1,10 do
print(PopLeft(DL).."\n")
end
PopLeft(Dl)
end
集合和包:
手册提到这个的时候就直接举了个例子,
如果要过滤出一段代码中所有的标识符,那么就要去掉其中的保留字。可以把保留字通过集合的方式存储,这样在判断一个字符串是不是保留字的时候不需要遍历tables,只需要看一下tables中有没有这个元素即可。
集合就是tables中所有的元素都用有意义的字符串作为索引。
字符串缓冲区:
以下的例子是说明,当处理大量字符串的时候,可以采用字符串缓冲区的办法。处理小的字符串,还是可以用简单的语句。
local buff = ""
for line in io.lines() do
buff = buff .. line .."\n"
end
function addString(stack,s)
table.insert(stack,s)
for i = table.getn(stack)-1,1,-1 do
if(string.len(stack[i]) > string.len(stack[i + 1])) then
break
end
stack[i] = stack[i] .. table.remove(stack)
end
end
local s = {}
for line in io.lines() do
addString(s,line .. "\n")
end
s = toString(s);
第一个for函数,读取一行字符串之后,直接用字符连接的符号加到原来的字符串上。当用这样的语句去读取一个350KB的文本的时候,它花了超过一分钟的时间。
原因:
lua是采用真正的垃圾算法,当它发现程序使用的内存过大的时候,会遍历所有数据结构,释放垃圾数据。
所以,当上述语句运行到一半的时候,我们假设它已经有50KB的字符长度了,后面又读取了20byte,计算机需要为这次操作先创建一个50020byte,把前面的50KB复制过来,然后加上新的20byte,老的50KB就变成了垃圾数据。可以想象当把整个350KB读取完成的时候,会产生大量的垃圾数据,而这个过程里lua会不停的遍历数据结构,释放垃圾数据。导致时间被大大加长,而且空间的使用量也大。
手册上提供了一种方法,缩短了时间,节省了读取过程中内存的使用。
通过一个栈的数据结构,把每一次读取的字符串和栈顶的字符串进行对比,如果新得到的字符串较长,那就进行合并操作,然后栈顶这个合并之后的新的字符串和栈里的下一个字符串进行比较,保证栈底部的字符串永远比上一级的长就可以。当整个文件读取完毕之后,在进行一次字符串的合并就可以了。通过这种方法,350KB的文件,只花费了0.5s。
在lua提供的标准函数io.read(*all)中,也是这么做的,但是它更快只需要0.02s,因为它的数据结构是由C语言实现的。