lua 习题与字符串加速复制

写这篇博客就是想向自己强调,时刻记住线性数据结构也是经常被加速处理的,这样时间复杂度可以从 O(N) 变成 O(logN)。
lua 字符串复制函数 string.rep 在指定字符串 s 后,可以将它复制指定数量。使用 c 语言时,最经常用的就是 memset 函数,用来初始化一块内存。不管是字符串复制,还是初始化一块内存,我的想法都是一个 for 循环遍历然后赋值就完事了吧。直到今天看到了 Programming in Lua 4th 书中习题 16.2 。这道题目就是不用 for 循环,让你写一段代码,可以产生字符串复制功能的 lua 代码,然后 lua 解释器执行这段代码实现字符串复制。当时习题给了正常情形下字符串复制的实现。代码如下所以。

function stringrep(s, n)
	local r = ""
	if n > 0 then
		while n > 1 do
			if n % 2 ~= 0 then r = r .. s end
			s = s .. s
			n = math.floor(n / 2)
		end
		r = r .. s
	end
	return r
end

先不要考虑字符串连接操作符 .. 的效率问题。复制单个字符,然后使用 # 操作符求字符串中字符的个数,即可验证功能是否正确。这段代码的时间复杂度不是 O(N) 而是 O(logN) 。不愧是一帮数学很厉害的大佬写的代码。这段代码用 2 的指数的和来组成整数 n 。指数最大的值就是以 2 为底 n 的对数的整数部分,比如以 2 为底时 9 的对数的整数部分是 3 而 8 的对数刚好是 3 。假设以 2 为底函数中 n 的对数的整数部分值是 k ,于是 n 被用如下各值的和表示 2^k + [2^i0 + 2^i1 + 2^i2 + ...] 。方括号仅仅表示可选部分,而 i0 i1 ... 等等这些值从 0 开始,每次循环 +1 但不必连续,需要时才会被累加到结果中。下面来举例说明一下。
7 可以被拆分成 2^2 + 2^0 + 2^1 而 12 可以被拆分成 2^3 + 2^2 而 31 可以被拆分成 2^4 + 2^0 +2^1 + 2^2 + 2^3 。比如以 2 为底 31 的对数整数部分是 4 这样还剩下 15 但代码 if n % 2 ~= 0 then r = r .. s end 刚好可以正确的处理余下的非 2 的幂部分,于是 15 被正确的处理了。真的很巧妙。最重要的是时间复杂度从线性曲线降低到了对数曲线。

分析完字符串复制的加速部分后,继续看一下习题,习题让产生序列来完成字符串复制。如下例子。将字符串复制 5 次生成的函数应该下面的样子。

function stringrep_5 (s)
	local r = ""
	r = r .. s
	s = s .. s
	s = s .. s
	r = r .. s
	return r
end

如何生成这段代码呢。其实很简单。我们依旧使用 O(logN) 时间复杂度生成。先生成函数 stringrep_sequence 的代码,然后 load 并执行,便可进行字符串的复制。

function generate_stringrep(n)
	local content = {}
	if n > 0 then
		table.insert(content, 1, "function stringrep_sequence(s)")
		table.insert(content, "\tlocal r = \"\"")
		while n > 1 do
			if n % 2 ~= 0 then
				table.insert(content, "\tr = r .. s")
			end
			table.insert(content, "\ts = s .. s")
			n = math.floor(n / 2)
		end
		table.insert(content, "\tr = r .. s")
		table.insert(content, "\treturn r")
		table.insert(content, "end")
	end
	
	local code = table.concat(content, "\n")
	return code
end

下面运行来比较一下结果。

local n, startc, endc = 999900000
startc = os.clock()
local str = stringrep("s", n)
endc = os.clock()
print(#str, "use sec:", endc - startc)

local code = generate_stringrep(n)
pcall(assert(load(code)))
startc = os.clock()
local str1 = stringrep_sequence("j")
endc = os.clock()
print(#str1, "use sec:", endc - startc)

几次运行结果如下。不要关注具体的时间,比较两种方式的耗时即可。后面两次执行猜测是缓存造成的速度加快了吧。很明显后一种方式会快些,是因为不需要分支判断吧。

E:\workplace>lua tmp.lua
999900000       use sec:        2.505
999900000       use sec:        1.325

E:\workplace>lua tmp.lua
999900000       use sec:        1.197
999900000       use sec:        1.098

E:\workplace>lua tmp.lua
999900000       use sec:        1.18
999900000       use sec:        1.05

转载于:https://my.oschina.net/iirecord/blog/1538628

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值