快速掌握Lua 5.3 —— 数据结构

Q:如何使用”table”实现一维数组?

A:

-- 使用构造的方式。
squares = {1, 4, 9, 16, 25, 36, 49, 64, 81}

-- 使用"for"循环 + 连续的索引。
a = {}    -- new array
for i=1, 1000 do
  a[i] = 0
end

-- 当然,连续的索引非必须都是正数。
a = {}
for i=-5, 5 do
  a[i] = 0
end

Q:如何使用”table”实现多维数组?

A:

-- 这里以二维数组举例,多维数组原理相同。
-- 使用嵌套"table"的方式。
mt = {}          -- create the matrix
for i=1, N do
  mt[i] = {}     -- create a new row
  for j = 1, M do
    mt[i][j] = 0
  end
end

Q:如何使用”table”实现链表?

A:

list = nil
-- 插入。
list = {next = list, value = v}
-- 遍历。
local l = list
while l do
  print(l.value)
  l = l.next
end

在Lua中你很少会需要使用链表,因为有比使用链表组织数据更好的方式。比如实现一个栈,可以使用一个数组加上一个存储栈顶索引的变量,而无需使用链表。

Q:如何使用”table”实现队列和栈?

A:以下这个例子即实现了队列,又实现了栈,

-- 为了避免污染全局空间,将函数和数据都存放在了名为List的table中。
List = {}
function List.new ()
  return {first = 0, last = -1}
end

function List.pushleft (list, value)
  local first = list.first - 1
  list.first = first
  list[first] = value
end

function List.pushright (list, value)
  local last = list.last + 1
  list.last = last
  list[last] = value
end

function List.popleft (list)
  local first = list.first
  if first > list.last then error("list is empty") end
  local value = list[first]
  list[first] = nil        -- to allow garbage collection
  list.first = first + 1
  return value
end

function List.popright (list)
  local last = list.last
  if list.first > last then error("list is empty") end
  local value = list[last]
  list[last] = nil         -- to allow garbage collection
  list.last = last - 1
  return value
end

当作为队列使用时,可以使用pushright()入队,使用popleft()出队(或者pushleft()入队,popright()出队)。
当作为栈使用时,可以使用pushright()入栈,使用popright()出栈(或者pushleft()入队,popright()出队)。

Q:如何使用”table”实现集合?

A:将集合中元素的名称作为”table”的索引,并为”table”中该元素的值赋值为true

--[[ "while""end""function""local"都是Lua的关键字,
     所以不能直接写成"while = true"的样式,会报错。]]
reserved = {
  ["while"] = true,     ["end"] = true,
  ["function"] = true,  ["local"] = true,
  my_identifiers = true, 
}

-- 可以快速判断某一元素是否在集合中。
if reserved[w] then    -- 在集合中。
   ...
else    -- 不在集合中。
   ...
end

Q:如何使用”table”实现字符串缓存?

A:以下例子是从文件中一行一行读取数据,最后将读取的数据组合为一个字符串整体,

local t = {}
for line in io.lines("file") do    -- 从文件中一行一行的读取数据。
    table.insert(t, line)    -- 读取的每一行数据作为"table"的一个成员。
end
--[[ "table.concat()"对于"table"中最后一个字符串后不会添加分隔符,
     所以在"t"的最后插入一个空字符串,
     让"table.concat()"在这个空字符串与原本的最后一个字符串之间插入分隔符。]]
table.insert(t, "")
--[[ "table.concat()"将"t"中所有字符串成员连接在一起,
     并在每个字符串之间添加指定的分隔符。]]
s = table.concat(t, "\n")

附加:

1、”table”不是Lua中的一种数据结构,而是Lua中唯一的数据结构。许多其他语言提供的数据结构,比如array,list,queue,set等等都可以在Lua使用”table”高效的实现。
2、创建集合还可以使用辅助函数的方式来实现,

function Set (list)
  local set = {}
  for _, l in ipairs(list) do set[l] = true end
  return set
end
reserved = Set{"while", "end", "function", "local", }

3、实现字符串缓存有一种不使用”table”的方式,

-- WARNING: bad code ahead!!
local buff = ""
for line in io.lines("file") do
    buff = buff .. line .. "\n"
end

这种实现方式对于小文件来说性能还可以接受,但对于大文件来说性能会非常低。在我的机器上测试,使用这种方式读取一个1M大小,20000行的文件耗时5.1秒,而用”Q & A”的例子中的程序读取相同的文件仅耗时0.030秒。
为什么会有这么大的差距?罪魁祸首是字符串连接符..
Lua使用真正的垃圾收集算法,当他发现程序使用太多的内存时,他就会遍历程序使用的所有数据结构,并释放那些不再被使用的数据结构(垃圾)。一般情况下,这个算法有很好的性能(Lua的高效并非偶然),但是上面那段代码的循环使得算法的效率极其低下。
为了理解现象的本质,假定我们的for循环已经运行了一会儿,buff中已经存储了50KB的字符串,每一行的大小为20bytes。当程序再次执行到buff..line.."\n"时,他创建了一个新的大小为50,020bytes的字符串(Lua中所有的字符串都是常量,不能改变其中的任意字符,所以..是创建了一个新的字符串),并且从buff中将50KB的字符串拷贝到新串中。也就是说,对于每一行都要移动50KB的内存,并且会越来越多。读取到100行的时候(仅仅2KB),Lua已经移动了5MB的内存。
这些内存拷贝仅仅是一部分原因,使情况变的更遭的是下面的赋值语句,
buff = buff .. line .. "\n"
旧的字符串变成了垃圾数据,两轮循环之后将有两个旧字符串包含超过100KB的垃圾数据。这个时候Lua会做出正确的决定,进行他的垃圾收集并释放这100KB的内存。问题在于每两次循环Lua就要进行一次垃圾收集,读取到20000行的时候需要进行10000次垃圾收集,回收的内存高达1GB,光这些被回收的内存就已经是原文件大小的1000倍了。
4、如果一定要用..的方式实现字符串缓存,我们将使用一种稍微高效些的算法。最初的算法通过将循环每一行的字符串连接到旧字符串上来解决问题,新的算法将避免如此。它连接两个短字符串成为一个稍长的字符串,然后连接两个稍长的字符串成为更长的字符串。算法的核心是用一个栈,在栈的底部用来保存已经生成的长字符串,而短的字符串从栈顶入栈。栈的状态变化和经典的汉诺塔问题类似:位于栈下面的字符串肯定比上面的长。只要一个字符串入栈后比它下面的字符串长,那么就将这两个字符串合并成一个新的更长的字符串。新生成的字符串继续与相邻的字符串比较,如果长于下面的字符串将继续进行合并,循环进行到没有串可以合并或者到达栈底为止。

function newStack ()
  return {""}   -- starts with an empty string
end

function addString (stack, s)
  table.insert(stack, s)    -- push 's' into the the stack
  for i = #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 = newStack()
for line in io.lines("file") do
    addString(s, line)
end
s = table.concat(s)

使用这种实现方式读取与”Q & A”中相同的文件耗时0.055秒。
5、如果读取文件,最快的方式还是io.read(*all)

io.input("file")
t = io.read("*all")

使用io.read(*all)读取与”Q & A”中相同的文件仅耗时0.007秒。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值