Lua 字符串迭代方式
最近一直有同学在私下问我关于Lua
字符串迭代的方式, 我觉得有必要和大家聊一下关于为字符串迭代聊一个使用场景.
迭代器
如果我们使用C/C++
, 那么非常简单可以使用那些数字索引的loop
来完成. 且这样的迭代、比较效率也非常高. 然而ANSIC
字符并不能完全符合业务多变的复杂环境, 所以我们也需要考虑高级、方便的使用方法.
参考其它编程语言我们可以发现, 其中有绝大部分使用的是迭代器思想. 即: 给出迭代函数, 通过循环来不断传入对象后调用迭代函数来完成. 这种方式是值得借鉴的, 即使在Lua
内我们需要一些trick
才能完成.
Lua
内使用迭代方式目前存在以下几种使用方式:
-
for i = start, stop, step do ... end
-
for index, value in ipairs(..) do ... end
-
for ... in iterator function do ... end
如果没有规定业务级别的特殊情况, 建议大家使用第1
种来完成. 理由当然是效率最高, 编码也非常简单易懂. 但是当你不考虑性能问题且觉得迭代需要统一使用ipairs
完成的时候, 只需要在string
的元方法内实现利于ipairs
迭代字符串
的判断即可.
第3
种方式其实我们可以通过string.gmatch
来实现! 但是我个人并不希望使用正则或匹配模式实现, 且我希望它能提供丰富的特性来适用于更多的场景.
1. 迭代器
既然自定义一个iterator
才能完成我们的需求那就直接动手就好了. 首先, 我们规划了以下需求:
-
字符串必须支持
ANSIC
字符迭代与UTF-8
字符两种. -
字符串迭代出错必须不影响流程.
-
字符串迭代对性能影响不能太大.
现在需求知道了! 那么先定义函数来规定我们如何使用:
---comment 可以通过迭代函数逐个字符迭代
---@param text string @待迭代的内容
---@param u8 boolean? @检查UTF8字符(可选, 默认为`false`)
---@return function
function string.iterator(text, u8)
-- TODO
end
普通ANSIC
字符迭代我们可以直接string.sub
分割即可, UTF-8
字符则可以使用utf8
可以来根据位置与数字编码来确定. 最后我们可以根据开始位置加上每次迭代字符的位置计算是否到达字符串结尾. 这样我们的迭代器的功能就算完成了, 大致的伪代码如下:
if u8 then
-- call utf8.code got code.
-- call utf8.char got char.
-- ret
end
-- sub (text, index, index)
-- ret
字符串内出现中文应该并不少见, 但是我们在迭代器中如果抛出捕获和调试代码会变得非常难看. 如果有必要, 我们在迭代器内迭代UTF-8
字符串的时候可以这样操作:
if not valid_u8_char then
return index, false, char
end
return index, true, char
第1
个返回值是utf8
字符串的索引位置, 第2
个值在按utf8
字符串规则迭代时发现了非法字符则为false
反之为true
. 第3
个返回值就是实际字符. 这种方式可以让开发者在循环期间就能判断, 并且拿到合适的值后再进行选择性跳出(或忽略). 最后我们来看看示例代码与结果:
local s = "我是谁?\x80\x80"
for index, valid, str in string.iterator(s, true) do
print(index, valid, str)
end
1 true 我
4 true 是
7 true 谁
10 true ?
13 false �
14 false �
可以看到结果完全满足我们的构想.
2. 索引
众所周知, ipairs
在5.3开始就废弃了__ipairs
原方法而使用__index
来索引迭代. 而string
并未完成实际意义上的数字索引下标查找字符的特性. 那么, 我们就只能通过重写__index
方法来完成了.
首先我们必须通过getmetatable
拿到string
的元表, 这样就能hook
所有字符串向上索引的操作. 然后预留好我们所需的代码位置, 在条件不满足时交给string
来完成后续操作.
getmetatable("").__index = function (text, key)
-- hook start
-- ...
-- hook stop
return string[key]
end
最后我们来补齐中间的代码:
if key == 'integer' then
if key > text.len then
-- return nil that let's stoped
end
-- sub(text, key, key)
-- ret
end
实现代码完成! 现在我们可以尝试编写一些示例来测试:
local s = "abcdefg"
for index, value in ipairs(s) do
print(index, value)
end
1 a
2 b
3 c
4 d
5 e
6 f
7 g
最后
之前所在项目内并无对迭代大量字符串的需求, 所以C API
来完成上述功能并不是那么必要.