迭代包括空白行在内的所有行。

给出一个带有一些空行的多行字符串,如何在 Lua 中迭代行,包括空行?

local s = "foo\nbar\n\njim"
for line in magiclines(s) do
  print( line=="" and "(blank)" or line)
end
--> foo
--> bar
--> (blank)
--> jim

这段代码不包括空行:

for line in string.gmatch(s,'[^\r\n]+') do print(line) end
--> foo
--> bar
--> jim

这段代码包括多余的空行:

for line in string.gmatch(s,"[^\r\n]*") do
  print( line=="" and "(blank)" or line)
end
--> foo
--> (blank)
--> bar
--> (blank)
--> (blank)
--> jim
--> (blank)
点赞
用户2633423
用户2633423

让我们看看这个 magiclines 的实现是否符合你的要求:

local function magiclines( str )
    local pos = 1;
    return function()
        if not pos then return nil end
        local  p1, p2 = string.find( str, "\r?\n", pos )
        local line
        if p1 then
            line = str:sub( pos, p1 - 1 )
            pos = p2 + 1
        else
            line = str:sub( pos )
            pos = nil
        end
        return line
    end
end

你可以用以下代码测试它:

local text = [[
foo
bar

jim

woof
]]

for line in magiclines( text ) do
    print( line=="" and "(blank)" or line)
end

输出:

foo
bar
(blank)
jim
(blank)
woof
(blank)
2013-10-11 20:59:57
用户415823
用户415823

以下模式应匹配每行,包括空行,只有一个限制条件:字符串必须包含终止的 CRLF

local s = "foo\nbar\n\njim\n" -- added terminating \n

for line in s:gmatch("([^\r\n]*)[\r\n]") do
   print(line == "" and "(blank)" or line)
end

--> foo
--> bar
--> (blank)
--> jim

一个替代模式,不需要额外的 CRLF,将产生一个空白行作为最后一行(因为捕获没有东西是可以接受的)。

local s = "foo\nbar\n\njim"

for line in s:gmatch("([^\r\n]*)[\r\n]?") do
   print(line == "" and "(blank)" or line)
end

--> foo
--> bar
--> (blank)
--> jim
--> (blank)
2013-10-11 21:42:55
用户107090
用户107090

尝试一下:

function magiclines(s)
        如果s:sub(-1)~="\n" then s=s.."\n" end
        返回 s:gmatch("(.-)\n")
end
2013-10-12 01:40:41
用户441830
用户441830

以下是一个使用 LPEG 的解决方案:

local lpeg      = require "lpeg"
local lpegmatch = lpeg.match
local P, C      = lpeg.P, lpeg.C

local iterlines
do
  local eol  = P"\r\n" + P"\n\r" + P"\n" + P"\r"
  local line = (1 - eol)^0
  iterlines = function (str, f)
    local lines = ((line / f) * eol)^0 * (line / f)
    return lpegmatch (lines, str)
  end
end

你得到的是一个可以替代迭代器的函数。第一个参数是你想迭代的字符串,第二个参数是每个匹配的操作:

--- 打印每一行
iterlines ("foo\nbar\n\njim\n\r\r\nbaz\rfoo\n\nbuzz\n\n\n\n", print)

--- 在 printf 的同时计数行数
local n = 0
iterlines ("foo\nbar\nbaz", function (line)
  n = n + 1
  io.write (string.format ("[%2d][%s]\n", n, line))
end)
2013-10-12 12:13:26
用户1244588
用户1244588

这里是另一种lPeg的解决方案,因为似乎我和phg在同时进行。但是由于语法更美观,我仍然会给你!

local lpeg = require "lpeg"
local C, V, P = lpeg.C, lpeg.V, lpeg.P

local g = P({ "S",
    S = (C(V("C")^0) * V("N"))^0 * C(V("C")^0),
    C = 1 - V("N"),
    N = P("\r\n") + "\n\r" + "\n" + "\r",
})

像这样使用它:

local test = "Foo\n\nBar\rfoo\r\n\n\n\rbar"
for k,v in pairs({g:match(test)}) do
    print(">", v);
end

或者当然,只需print(g:match(test))

2013-10-12 12:30:44
用户19843756
用户19843756
  • 遍历 text 中的所有项,使用方法如下:
    • 选项:
      • separator = "pattern string",默认值为 \n
      • plain = boolean,默认值为 nil,用于 string.find
      • no_item = anything,默认值为 nil,它是前一项和后一项的返回值 [1] 和 [last]
  • 对于 TextIterator(s, { options }) 中的每一个项和状态,使用以下方式:
    • item - 不包括分隔符的内容
    • state.item_num - 从 1 开始的项数
    • state.previous_item - 前面的项
    • state.next_item - 后面的项
    • state.separator - 匹配的分隔符
    • state.is_last - 是否是最后一项
local function TextIterator(s, options)
    local options = options or {}
    local state = {text = s, begin=1, next_begin=1,
            item_num=-1, next_item=options.no_item, is_last=false}

    local function get_item(state)
        local text = state.text
        state.begin = state.next_begin
        state.previous_item = state.item
        state.item = state.next_item
        state.separator = state.next_separator
        state.item_num = state.item_num + 1
        if state.begin == -1 then
            state.next_item = options.no_item
            state.next_begin = -2
            state.is_last = true
            return state.item, state
        elseif state.begin == -2 then
            return nil
        end

        local b, e = text:find(options.separator,
            state.next_begin, options.plain)
        if b then
            if options.plain then
                state.next_separator = options.separator
            else
                state.next_separator = string.match(text,
                "("..options.separator..")", state.next_begin)
            end
            state.next_begin = e+1
            state.next_item = text:sub(state.begin,
                e-string.len(state.next_separator))
            return state.item, state
        else
            state.next_separator = ""
            state.next_begin = -1
            state.next_item = text:sub(state.begin)
            return state.item, state
        end
    end

    if not options.separator then options.separator = "\n" end

    get_item(state) -- 初始化
    return get_item, state
end

txt = "a,b;c.d/e.f.g"

for item, state in TextIterator(txt, { separator="%p", plain=false, no_item=nil }) do
    print(item, state.item_num, state.separator,
        state.previous_item, state.next_item, state.is_last)
end

>lua -e "io.stdout:setvbuf 'no'" "txtiterator.lua"
a   1   ,   nil b   false
b   2   ;   a   c   false
c   3   .   b   d   false
d   4   /   c   e   false
e   5   .   d   f   false
f   6   .   e   g   false
g   7       f   nil true
>Exit code: 0
2022-08-25 08:22:05