Разделить строку в Lua?

Мне нужно сделать простое разбиение строки, но, похоже, для этого не существует функции, а ручной метод, который я тестировал, не сработал. Как бы я это сделал?

21 ответ

Решение

Пожалуйста, смотрите разделение строк:

Вот различные способы разбить строку на список подстрок, разбить исходную строку при появлении некоторого разделителя (символа, набора символов или шаблона). Обычно это называется функцией разделения строк [2].

Вот мое действительно простое решение. Используйте функцию gmatch для захвата строк, которые содержат как минимум ОДИН символ НИЧЕГО, кроме требуемого разделителя. Разделителем является ЛЮБОЙ пробел (%s в Lua) по умолчанию:

function mysplit(inputstr, sep)
        if sep == nil then
                sep = "%s"
        end
        local t={} ; i=1
        for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
                t[i] = str
                i = i + 1
        end
        return t
end

Если вы разбиваете строку в Lua, вам следует попробовать методы string.gmatch () или string.sub(). Используйте метод string.sub(), если вам известен индекс, по которому вы хотите разделить строку, или используйте string.gmatch (), если вы проанализируете строку, чтобы найти место для разделения строки.

Пример использования string.gmatch () из справочного руководства Lua 5.1:

 t = {}
 s = "from=world, to=Lua"
 for k, v in string.gmatch(s, "(%w+)=(%w+)") do
   t[k] = v
 end

Если вы просто хотите перебрать токены, это довольно аккуратно:

line = "one, two and 3!"

for token in string.gmatch(line, "[^%s]+") do
   print(token)
end

Выход:

один,

два

а также

3!

Краткое объяснение: шаблон "[^%s]+" соответствует каждой непустой строке между пробелами.

Как только string.gmatch найдет шаблоны в строке, эта функция найдет вещи между шаблонами:

function string:split(pat)
  pat = pat or '%s+'
  local st, g = 1, self:gmatch("()("..pat..")")
  local function getter(segs, seps, sep, cap1, ...)
    st = sep and seps + #sep
    return self:sub(segs, (seps or 0) - 1), cap1 or sep, ...
  end
  return function() if st then return getter(st, g()) end end
end

По умолчанию он возвращает все, что разделено пробелом.

Если вы программируете на Lua, вам здесь не повезло. Lua - это тот язык программирования, который стал печально известным, потому что его авторы никогда не реализовывали "функцию разделения" в стандартной библиотеке, а вместо этого написали 16 экранов с объяснениями и неубедительными отговорками, почему они этого не сделали и не будут. перемежается многочисленными полуработающими примерами, которые практически гарантированно работают почти для всех, но не подходят для вашего углового случая. Это просто современный язык Lua, и каждый, кто программирует на Lua, просто стискивает зубы и перебирает символы. Существует множество решений, которые иногда лучше, но ровно ноль решений, которые надежно лучше.

Вот функция:

function split(pString, pPattern)
   local Table = {}  -- NOTE: use {n = 0} in Lua-5.0
   local fpat = "(.-)" .. pPattern
   local last_end = 1
   local s, e, cap = pString:find(fpat, 1)
   while s do
      if s ~= 1 or cap ~= "" then
     table.insert(Table,cap)
      end
      last_end = e+1
      s, e, cap = pString:find(fpat, last_end)
   end
   if last_end <= #pString then
      cap = pString:sub(last_end)
      table.insert(Table, cap)
   end
   return Table
end

Назовите это как:

list=split(string_to_split,pattern_to_match)

например:

list=split("1:2:3:4","\:")


Для получения дополнительной перейдите сюда:
http://lua-users.org/wiki/SplitJoin

Поскольку существует более одного способа снятия шкуры с кошки, вот мой подход:

Код:

#!/usr/bin/env lua

local content = [=[
Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna 
aliqua. Ut enim ad minim veniam, quis nostrud exercitation 
ullamco laboris nisi ut aliquip ex ea commodo consequat.
]=]

local function split(str, sep)
   local result = {}
   local regex = ("([^%s]+)"):format(sep)
   for each in str:gmatch(regex) do
      table.insert(result, each)
   end
   return result
end

local lines = split(content, "\n")
for _,line in ipairs(lines) do
   print(line)
end

Выход: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Пояснение:

gmatch функция работает как итератор, она выбирает все подходящие строки regex, regex принимает все символы, пока не найдет разделитель.

Многие из этих ответов принимают только односимвольные разделители или плохо разбираются в крайних случаях (например, пустые разделители), поэтому я подумал, что предоставлю более определенное решение.

Вот две функции, gsplit а также split, адаптированный из кода в расширении Scribunto MediaWiki, которое используется в вики, таких как Википедия. Код распространяется по лицензии GPL v2. Я изменил имена переменных и добавил комментарии, чтобы немного облегчить понимание кода, и я также изменил код, чтобы использовать обычные строковые шаблоны Lua вместо шаблонов Scribunto для строк Unicode. Оригинальный код имеет тестовые случаи здесь.

-- gsplit: iterate over substrings in a string separated by a pattern
-- 
-- Parameters:
-- text (string)    - the string to iterate over
-- pattern (string) - the separator pattern
-- plain (boolean)  - if true (or truthy), pattern is interpreted as a plain
--                    string, not a Lua pattern
-- 
-- Returns: iterator
--
-- Usage:
-- for substr in gsplit(text, pattern, plain) do
--   doSomething(substr)
-- end
local function gsplit(text, pattern, plain)
  local splitStart, length = 1, #text
  return function ()
    if splitStart then
      local sepStart, sepEnd = string.find(text, pattern, splitStart, plain)
      local ret
      if not sepStart then
        ret = string.sub(text, splitStart)
        splitStart = nil
      elseif sepEnd < sepStart then
        -- Empty separator!
        ret = string.sub(text, splitStart, sepStart)
        if sepStart < length then
          splitStart = sepStart + 1
        else
          splitStart = nil
        end
      else
        ret = sepStart > splitStart and string.sub(text, splitStart, sepStart - 1) or ''
        splitStart = sepEnd + 1
      end
      return ret
    end
  end
end

-- split: split a string into substrings separated by a pattern.
-- 
-- Parameters:
-- text (string)    - the string to iterate over
-- pattern (string) - the separator pattern
-- plain (boolean)  - if true (or truthy), pattern is interpreted as a plain
--                    string, not a Lua pattern
-- 
-- Returns: table (a sequence table containing the substrings)
local function split(text, pattern, plain)
  local ret = {}
  for match in gsplit(text, pattern, plain) do
    table.insert(ret, match)
  end
  return ret
end

Некоторые примеры split используемая функция:

local function printSequence(t)
  print(unpack(t))
end

printSequence(split('foo, bar,baz', ',%s*'))       -- foo     bar     baz
printSequence(split('foo, bar,baz', ',%s*', true)) -- foo, bar,baz
printSequence(split('foo', ''))                    -- f       o       o

Мне нравится это короткое решение

function split(s, delimiter)
    result = {};
    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
        table.insert(result, match);
    end
    return result;
end

Вы можете использовать этот метод:

function string:split(delimiter)
  local result = { }
  local from  = 1
  local delim_from, delim_to = string.find( self, delimiter, from  )
  while delim_from do
    table.insert( result, string.sub( self, from , delim_from-1 ) )
    from  = delim_to + 1
    delim_from, delim_to = string.find( self, delimiter, from  )
  end
  table.insert( result, string.sub( self, from  ) )
  return result
end

delimiter = string.split(stringtodelimite,pattern) 

Путь не виден в других

function str_split(str, sep)
    if sep == nil then
        sep = '%s'
    end 

    local res = {}
    local func = function(w)
        table.insert(res, w)
    end 

    string.gsub(str, '[^'..sep..']+', func)
    return res 
end

Вы можете использовать библиотеку фонарика. У этого есть функция для разделения строки с помощью разделителя, который выводит список.

В нем реализованы многие функции, которые могут понадобиться нам при программировании и которые отсутствуют в Lua.

Вот пример его использования.

> 
> stringx = require "pl.stringx"
> 
> str = "welcome to the world of lua"
> 
> arr = stringx.split(str, " ")
> 
> arr
{welcome,to,the,world,of,lua}
> 

Просто сидим на разделителе

local str = 'one,two'
local regxEverythingExceptComma = '([^,]+)'
for x in string.gmatch(str, regxEverythingExceptComma) do
    print(x)
end

Очень поздно ответить на этот вопрос, но в случае, если кому-то нужна версия, которая обрабатывает количество разделений, которое вы хотите получить...

-- Split a string into a table using a delimiter and a limit
string.split = function(str, pat, limit)
  local t = {}
  local fpat = "(.-)" .. pat
  local last_end = 1
  local s, e, cap = str:find(fpat, 1)
  while s do
    if s ~= 1 or cap ~= "" then
      table.insert(t, cap)
    end

    last_end = e+1
    s, e, cap = str:find(fpat, last_end)

    if limit ~= nil and limit <= #t then
      break
    end
  end

  if last_end <= #str then
    cap = str:sub(last_end)
    table.insert(t, cap)
  end

  return t
end

Я использовал приведенные выше примеры для создания своей собственной функции. Но пропавшая часть для меня автоматически убегала от магических персонажей.

Вот мой вклад:

function split(text, delim)
    -- returns an array of fields based on text and delimiter (one character only)
    local result = {}
    local magic = "().%+-*?[]^$"

    if delim == nil then
        delim = "%s"
    elseif string.find(delim, magic, 1, true) then
        -- escape magic
        delim = "%"..delim
    end

    local pattern = "[^"..delim.."]+"
    for w in string.gmatch(text, pattern) do
        table.insert(result, w)
    end
    return result
end

В зависимости от варианта использования это может быть полезно. Он обрезает весь текст по обе стороны от флагов:

b = "This is a string used for testing"

--Removes unwanted text
c = (b:match("a([^/]+)used"))

print (c)

Выход:

string

Для тех, кто пришел к упражнению 10.1 книги "Программирование на Lua", кажется очевидным, что мы не можем использовать понятие, описанное далее в книге (итератор), и что функция должна принимать более одного разделителя символов.

The split()это трюк, чтобы получить шаблон, соответствующий тому, что не нужно (разделение), и вернуть пустую таблицу в пустой строке. ВозвращениеplainSplit()больше похоже на раскол на другом языке.

      magic = "([%%%.%(%)%+%*%?%[%]%^%$])"

function split(str, sep, plain)
    if plain then sep = string.gsub(sep, magic, "%%%1") end
    
    local N = '\255'
    str = N..str..N
    str = string.gsub(str, sep, N..N)

    local result = {}
    for word in string.gmatch(str, N.."(.-)"..N) do
        if word ~= "" then
            table.insert(result, word)
        end
    end
    return result
end


function plainSplit(str, sep)
    sep = string.gsub(sep, magic, "%%%1")

    local result = {}
    local start = 0
    repeat
        start = start + 1

        local from, to = string.find(str, sep, start)
        from = from and from-1
        
        local word = string.sub(str, start, from, true)
        table.insert(result, word)

        start = to
    until start == nil

    return result
end


function tableToString(t)
    local ret = "{"
    for _, word in ipairs(t) do
        ret = ret .. '"' .. word .. '", '
    end
    ret = string.sub(ret, 1, -3)
    ret = ret .. "}"

    return #ret > 1 and ret or "{}"
end

function runSplit(func, title, str, sep, plain)
    print("\n" .. title)
    print("str: '"..str.."'")
    print("sep: '"..sep.."'")
    local t = func(str, sep, plain)
    print("-- t = " .. tableToString(t))
end



print("\n\n\n=== Pattern split ===")
runSplit(split, "Exercice 10.1", "a whole new world", " ")
runSplit(split, "With trailing seperator", "  a  whole   new world  ", " ")
runSplit(split, "A word seperator", "a whole new world", " whole ")
runSplit(split, "Pattern seperator", "a1whole2new3world", "%d")
runSplit(split, "Magic characters as plain seperator", "a$.%whole$.%new$.%world", "$.%", true)
runSplit(split, "Control seperator", "a\0whole\1new\2world", "%c")
runSplit(split, "ISO Time", "2020-07-10T15:00:00.000", "[T:%-%.]")

runSplit(split, " === [Fails] with \\255 ===", "a\255whole\0new\0world", "\0", true)

runSplit(split, "How does your function handle empty string?", "", " ")



print("\n\n\n=== Plain split ===")
runSplit(plainSplit, "Exercice 10.1", "a whole new world", " ")
runSplit(plainSplit, "With trailing seperator", "  a  whole   new world  ", " ")
runSplit(plainSplit, "A word seperator", "a whole new world", " whole ")
runSplit(plainSplit, "Magic characters as plain seperator", "a$.%whole$.%new$.%world", "$.%")

runSplit(plainSplit, "How does your function handle empty string?", "", " ")

выход

      === Pattern split ===

Exercice 10.1
str: 'a whole new world'
sep: ' '
-- t = {"a", "whole", "new", "world"}

With trailing seperator
str: '  a  whole   new world  '
sep: ' '
-- t = {"a", "whole", "new", "world"}

A word seperator
str: 'a whole new world'
sep: ' whole '
-- t = {"a", "new world"}

Pattern seperator
str: 'a1whole2new3world'
sep: '%d'
-- t = {"a", "whole", "new", "world"}

Magic characters as plain seperator
str: 'a$.%whole$.%new$.%world'
sep: '$.%'
-- t = {"a", "whole", "new", "world"}

Control seperator
str: 'awholenewworld'
sep: '%c'
-- t = {"a", "whole", "new", "world"}

ISO Time
str: '2020-07-10T15:00:00.000'
sep: '[T:%-%.]'
-- t = {"2020", "07", "10", "15", "00", "00", "000"}

 === [Fails] with \255 ===
str: 'a�wholenewworld'
sep: ''
-- t = {"a"}

How does your function handle empty string?
str: ''
sep: ' '
-- t = {}



=== Plain split ===

Exercice 10.1
str: 'a whole new world'
sep: ' '
-- t = {"a", "whole", "new", "world"}

With trailing seperator
str: '  a  whole   new world  '
sep: ' '
-- t = {"", "", "a", "", "whole", "", "", "new", "world", "", ""}

A word seperator
str: 'a whole new world'
sep: ' whole '
-- t = {"a", "new world"}

Magic characters as plain seperator
str: 'a$.%whole$.%new$.%world'
sep: '$.%'
-- t = {"a", "whole", "new", "world"}

How does your function handle empty string?
str: ''
sep: ' '
-- t = {""}

Есть пример(unexpandTabs) в конце раздела «Замены» книги «Программирование на Lua», 4-е изд., Глава 10, в которой используется SOHсимвол (), чтобы пометить столбцы табуляции для последующей обработки. Я подумал, что это отличная идея, поэтому адаптировал ее к идеям «соответствовать всему, кроме символа-разделителя», которые используются во многих ответах здесь. Предварительно обрабатывая входную строку для замены всех совпадений на , мы можем поддерживать произвольные шаблоны разделителей , что делают только некоторые ответы, например отличный ответ @norman-ramsey .

Я также включилexclude_emptyпараметр с поведением по умолчанию просто для развлечения.

Очевидно, что это приведет к неправильному выводу, если входная строка содержит\1, но это кажется крайне маловероятным в любом случае за пределами обмена специализированными протоколами.

      function string:split(pat, exclude_empty)
  pat = pat or "%s+"
  self = self:gsub(pat, "\1")
  local res = {}
  for match in self:gmatch("([^\1]" .. (exclude_empty and "+" or "*") .. ")") do
    res[#res + 1] = match
  end
  return res
end

Я обнаружил, что многие другие ответы имели крайние случаи, которые не удались (например, когда данная строка содержит#,{или}символы, или когда задан символ-разделитель, например%которые требуют побега). Вот реализация, с которой я пошел вместо этого:

      local function newsplit(delimiter, str)
    assert(type(delimiter) == "string")
    assert(#delimiter > 0, "Must provide non empty delimiter")

    -- Add escape characters if delimiter requires it
    delimiter = delimiter:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%0")

    local start_index = 1
    local result = {}

    while true do
       local delimiter_index, _ = str:find(delimiter, start_index)

       if delimiter_index == nil then
          table.insert(result, str:sub(start_index))
          break
       end

       table.insert(result, str:sub(start_index, delimiter_index - 1))

       start_index = delimiter_index + 1
    end

    return result
end

Вот процедура, которая работает в Lua 4.0, возвращая таблицу t подстрок в inputstr , разделенных sep :

      function string_split(inputstr, sep)
    local inputstr = inputstr .. sep
    local idx, inc, t = 0, 1, {}
    local idx_prev, substr
    repeat 
        idx_prev = idx
        inputstr = strsub(inputstr, idx + 1, -1)    -- chop off the beginning of the string containing the match last found by strfind (or initially, nothing); keep the rest (or initially, all)
        idx = strfind(inputstr, sep)                -- find the 0-based r_index of the first occurrence of separator 
        if idx == nil then break end                -- quit if nothing's found
        substr = strsub(inputstr, 0, idx)           -- extract the substring occurring before the separator (i.e., data field before the next delimiter)
        substr = gsub(substr, "[%c" .. sep .. " ]", "") -- eliminate control characters, separator and spaces
        t[inc] = substr             -- store the substring (i.e., data field)
        inc = inc + 1               -- iterate to next
    until idx == nil
    return t
end

Этот простой тест

      inputstr = "the brown lazy fox jumped over the fat grey hen ... or something."
sep = " " 
t = {}
t = string_split(inputstr,sep)
for i=1,15 do
    print(i, t[i])
end

Урожайность:

      --> t[1]=the
--> t[2]=brown
--> t[3]=lazy
--> t[4]=fox
--> t[5]=jumped
--> t[6]=over
--> t[7]=the
--> t[8]=fat
--> t[9]=grey
--> t[10]=hen
--> t[11]=...
--> t[12]=or
--> t[13]=something.
Другие вопросы по тегам