Сопоставить повторяемую строку как "целое слово" в Lua 5.1
Моя среда:
- Lua 5.1
- Абсолютно нельзя использовать библиотеки с собственным компонентом (например, C .so/.dll).
- Я могу запустить любой произвольный чистый код Lua 5.1, но не могу получить доступ
os
и несколько других пакетов, которые позволили бы получить доступ к собственной файловой системе, командам оболочки или чему-либо подобному, поэтому все функциональные возможности должны быть реализованы в самом Lua (только). - Мне уже удалось вытащить LuLpeg. Я, вероятно, могу использовать другие чистые библиотеки Lua.
Мне нужно написать функцию, которая возвращает true
если входная строка соответствует произвольной последовательности букв и цифр как целого слова, которое повторяется один или несколько раз и может иметь пунктуацию в начале или конце всей соответствующей подстроки. Я использую "целое слово" в том же смысле, что и граница слова PCRE \b
,
Чтобы продемонстрировать идею, вот неправильная попытка с использованием re
модуль LuLpeg; кажется, что он работает с негативными взглядами, но не с негативными взглядами:
function containsRepeatingWholeWord(input, word)
return re.match(input:gsub('[%a%p]+', ' %1 '), '%s*[^%s]^0{"' .. word .. '"}+[^%s]^0%s*') ~= nil
end
Вот примеры строк и ожидаемое возвращаемое значение (кавычки синтаксические, как если бы они были напечатаны в интерпретаторе Lua, а не литеральные части строки; это сделано для того, чтобы сделать пробелы в конце / в начале очевидными):
- вход:
" one !tvtvtv! two"
, слово:tv
, возвращаемое значение:true
- вход:
"I'd"
, слово:d
, возвращаемое значение:false
- вход:
"tv"
, слово:tv
, возвращаемое значение:true
- вход:
" tvtv! "
, слово:tv
, возвращаемое значение:true
- вход:
" epon "
, слово:nope
, возвращаемое значение:false
- вход:
" eponnope "
, слово:nope
, возвращаемое значение:false
- вход:
"atv"
, слово:tv
, возвращаемое значение:false
Если бы у меня была полная библиотека регулярных выражений PCRE, я мог бы сделать это быстро, но я этого не делаю, потому что не могу связаться с C, и я не нашел ни одной чистой реализации Lua PCRE или подобной.
Я не уверен, достаточно ли гибок LPEG (используя LPEG напрямую или через re
модуль), чтобы делать то, что я хочу, но я уверен, что встроенные функции Lua не могут делать то, что я хочу, потому что он не может обрабатывать повторяющиеся последовательности символов. (tv)+
не работает со встроенным Lua string:match
функция и тому подобное.
Интересные ресурсы, которые я искал, чтобы попытаться выяснить, как это сделать, но безрезультатно:
- http://www.inf.puc-rio.br/~roberto/lpeg/re.html
- http://www.lua.org/manual/5.2/manual.html
- http://lua-users.org/wiki/FrontierPattern (к сожалению, не поддерживается в моем переводчике)
- http://lua-users.org/wiki/PatternsTutorial
- http://www.gammon.com.au/lpeg
- http://lua-users.org/wiki/StringRecipes
- как проверить, появляется ли слово как целое слово в строке в Lua
2 ответа
Шаблоны Lua достаточно мощные.
LPEG здесь не нужен.
Это твоя функция
function f(input, word)
return (" "..input:gsub(word:gsub("%%", "%%%%"), "\0").." "):find"%s%p*%z+%p*%s" ~= nil
end
Это проверка функции
for _, t in ipairs{
{input = " one !tvtvtv! two", word = "tv", return_value = true},
{input = "I'd", word = "d", return_value = false},
{input = "tv", word = "tv", return_value = true},
{input = " tvtv! ", word = "tv", return_value = true},
{input = " epon ", word = "nope", return_value = false},
{input = " eponnope ", word = "nope", return_value = false},
{input = "atv", word = "tv", return_value = false},
} do
assert(f(t.input, t.word) == t.return_value)
end
Я думаю, что шаблон не работает надежно, потому что %s*[^%s]^0
part соответствует необязательному ряду пробельных символов, за которыми следуют непробельные символы, и затем он пытается найти совпадение с повторяющимся словом и завершается неудачно. После этого он не перемещается назад или вперед в строке и пытается сопоставить дублированное слово в другой позиции. Семантика LPeg и re
сильно отличаются от большинства движков регулярных выражений, даже если они похожи.
Вот re
версия В шаблоне есть один захват (сокращенное слово), поэтому, если найденное дублированное слово найдено, при сопоставлении возвращается строка, а не число.
function f(str, word)
local patt = re.compile([[
match_global <- repeated / ( [%s%p] repeated / . )+
repeated <- { %word+ } (&[%s%p] / !.) ]],
{ word = word })
return type(patt:match(str)) == 'string'
end
Это несколько сложно, потому что ваниль re
не может генерировать lpeg.B
шаблон.
Вот lpeg
версия с использованием lpeg.B
, LuLPeg также работает здесь.
local lpeg = require 'lpeg'
lpeg.locale(lpeg)
local function is_at_beginning(_, pos)
return pos == 1
end
function find_reduplicated_word(str, word)
local type, _ENV = type, math
local B, C, Cmt, P, V = lpeg.B, lpeg.C, lpeg.Cmt, lpeg.P, lpeg.V
local non_word = lpeg.space + lpeg.punct
local patt = P {
(V 'repeated' + 1)^1,
repeated = (B(non_word) + Cmt(true, is_at_beginning))
* C(P(word)^1)
* #(non_word + P(-1))
}
return type(patt:match(str)) == 'string'
end
for _, test in ipairs {
{ 'tvtv', true },
{ ' tvtv', true },
{ ' !tv', true },
{ 'atv', false },
{ 'tva', false },
{ 'gun tv', true },
{ '!tv', true },
} do
local str, expected = table.unpack(test)
local result = find_reduplicated_word(str, 'tv')
if result ~= expected then
print(result)
print(('"%s" should%s match but did%s')
:format(str, expected and "" or "n't", expected and "n't" or ""))
end
end