Я пытаюсь создать регулярное выражение, которое позволит мне разделить строки ниже только на центральную запятую.
str_1 <- "N(0, 1)"
str_2 <- "N(N(0.1, 1), 1)"
str_3 <- "N(U(0, 1), 1)"
str_4 <- "N(0, T(0, 1))"
str_5 <- "N(N(0, 1), N(0, 1))"
Думайте о них как о параметрах распределений. Теперь я хотел бы остановиться на запятой «высший уровень».
Некоторые детали: Числа могут быть десятичными числами, как положительными, так и отрицательными. Они всегда будут сгруппированы внутри U()
, N()
, LN()
или T()
и разделены запятой. Дополнительные группы будут добавлены позже, поэтому требуется более общее решение или легко расширяемое. Что я хочу сделать, так это разделить выражения на запятую «верхнего уровня».
Теперь первый случай str_1
прост, используя:
unlist(strsplit(str_1, ",", perl = TRUE))
Прежде чем я продолжу, мне нужно знать, есть ли у меня вложение. Я знаю, что у меня будет более одного N, U, LN или T, если есть вложенность. Итак, чтобы проверить, я сделал (для str_2
):
length(attr(gregexpr("(N|LN|U|T)", str_2, perl = TRUE)[[1]], "match.length")) > 1
Установив, есть ли у меня вложенность (может быть, это более чистый способ проверить это?), я могу приступить к разбиению оставшихся строк. Однако именно здесь я застрял. Учитывая, что я не могу считать запятые, так как случаи str_2
, str_3
и str_4
будут неоднозначными. Как мне убедиться, что я разделяю только центральную запятую?
Я ожидаю следующие результаты (таким образом, обрезав первую букву и круглую скобку и последнюю круглую скобку)
# str_2
"N(0.1, 1)" "1"
# str_3
"U(0, 1)" "1"
# str_4
"0" "T(0, 1)"
# str_5
"N(0, 1)" "N(0, 1)"
Я хотел бы остаться с базой R, чтобы уменьшить количество зависимостей для кода, если это возможно. Буду признателен за любую оказанную помощь. Также возможно, что это не решается регулярным выражением, но требует программного подхода, возможно, с помощью рекурсии, как это предлагается в этом вопросе Java.
strsplit(substring(str_2, 3, nchar(str_2)-1), ",(?![^()]*\\))", perl=TRUE)
Лучше всего использовать инструмент лексического анализа (lex) и анализатор грамматики (yacc). Однако существуют разновидности регулярных выражений, которые могут сканировать вложенные структуры. Также можно выполнять несколько синтаксических анализов с помощью любого механизма регулярных выражений: 1. аннотировать уровень вложенности каждой скобки, 2. сканировать открывающую и закрывающую скобку одного уровня, 3. удалять аннотацию вложенности. Подробности смотрите на twiki.org/cgi-bin/view/Blog/BlogEntry201109x3
Если ваши векторы символов имеют формат, который вы показали, вы можете добиться того, что вам нужно, с помощью одного регулярного выражения PCRE:
(?:\G(?!^)\s*,\s*|^N\()\K(?:\d+|\w+(\([^()]*(?:(?1)[^()]*)*\)))(?=\s*,|\)$)
Посмотрите демонстрацию регулярного выражения . Подробности
(?:\G(?!^)\s*,\s*|^N\()
- конец предыдущего успешного совпадения (\G(?!^)
), а затем запятая, заключенная с нулем или более пробелами (\s*,\s*
), или строка N(
в начале строки (^N\(
)\K
— оператор сброса совпадений, который отбрасывает весь текст, совпавший до сих пор, из текущего буфера памяти совпадений.(?:
- начало группы без захвата
\d+
- одна или несколько цифр|
- или\w+
- один или несколько символов слова(\([^()]*(?:(?1)[^()]*)*\))
- Группа 1 (необходима для правильной работы рекурсии): (
, затем любой ноль или более символов, кроме (
и )
, затем ноль или более вхождений шаблона группы 1 (рекурсивно), а затем ноль или более символов, кроме (
и )
, а затем символ )
)
- конец незахватывающей группы(?=\s*,|\)$)
- сразу же следует ноль или более пробелов, а затем запятая или )
символ в конце строки.Посмотрите демонстрацию регулярного выражения :
strs <- c("N(0, 1)", "N(N(0.1, 1), 1)", "N(U(0, 1), 1)", "N(0, T(0, 1))", "N(N(0, 1), N(0, 1))")
p <- "(?:\\G(?!^)\\s*,\\s*|^N\\()\\K(?:\\d+|\\w+(\\([^()]*(?:(?1)[^()]*)*\\)))(?=\\s*,|\\)$)"
regmatches(strs, gregexpr(p, strs, perl=TRUE))
# => [[1]]
# [1] "0" "1"
#
# [[2]]
# [1] "N(0.1, 1)" "1"
#
# [[3]]
# [1] "U(0, 1)" "1"
#
# [[4]]
# [1] "0" "T(0, 1)"
#
# [[5]]
# [1] "N(0, 1)" "N(0, 1)"
Спасибо за прекрасное объяснение компонентов выражения. Очень признателен.
Задайте s
как вектор символов строк. Мы вычисляем совокупное количество левых скобок минус совокупное количество правых скобок и заменяем любую запятую, для которой эта разница равна 0, точкой с запятой, а затем разделяем ее.
Для этого мы используем gsubfn
, который похож на gsub
, за исключением того, что замена не обязательно должна быть строкой, но может быть прото-объектом. Метод pre
прото-объекта запускается в начале каждой строки, а метод fun
запускается для каждого совпадения с шаблоном, переданным в gsubfn
. Метод pre
, определенный ниже, устанавливает lev
в 0, где lev
будет содержать кумулятивную разницу, описанную выше. fun
запускается при каждом совпадении с левой скобкой, правой скобкой или запятой, и каждый раз, когда мы получаем совпадение:
lev
lev
Удалите мусор в начале и конце ввода s
, используя sub
, запустите gsubfn
и затем разделите результат точкой с запятой. В конце мы упрощаем результат во фрейм данных. Каждый вектор выходных символов здесь имеет длину 2, но если они могут иметь разную длину, то опустите as.data.frame
.
library(gsubfn)
library(magrittr)
# s is char vec; rm is TRUE if 1st two chars & last one to be removed
# output is list of char vecs
Split <- function(s, rm = TRUE) {
p <- proto(
pre = function(this) this$lev <- 0,
fun = function(this, x) {
this$lev <- this$lev + ( x == "(" ) - ( x == ")" )
if (x == "," && this$lev == 0) ";" else x
}
)
if (rm) s <- sub("^..(.*).$", r"{\1}", s)
s %>% gsubfn(r"{[\(\),]}", p, .) %>% strsplit(" *; *")
}
# test 1
s <- c(str_1 = "N(0, 1)", str_2 = "N(N(0.1, 1), 1)", str_3 = "N(U(0, 1), 1)",
str_4 = "N(0, T(0, 1))", str_5 = "N(N(0, 1), N(0, 1))")
s %>% Split %>% as.data.frame
## str_1 str_2 str_3 str_4 str_5
## 1 0 N(0.1, 1) U(0, 1) 0 N(0, 1)
## 2 1 1 1 T(0, 1) N(0, 1)
Обратите внимание, что это может работать с любым количеством аргументов:
# test 2
w <- "lognormal(N(0, 1), 1), lognormal(0, U(0, 1)), beta(U(1, 1), 2), N(0, 1)"
w %>% Split(rm = FALSE) %>% unlist
## [1] "lognormal(N(0, 1), 1)" "lognormal(0, U(0, 1))" "beta(U(1, 1), 2)"
## [4] "N(0, 1)"
Если учесть, что структура осталась прежней, то можно было бы сделать:
lapply(parse(text=strings), function(x)c(deparse(x[[2]]), deparse(x[[3]])))
[[1]]
[1] "0" "1"
[[2]]
[1] "N(0.1, 1)" "1"
[[3]]
[1] "U(0, 1)" "1"
[[4]]
[1] "0" "T(0, 1)"
[[5]]
[1] "N(0, 1)" "N(0, 1)"
strings <- c("N(0, 1)", "N(N(0.1, 1), 1)", "N(U(0, 1), 1)", "N(0, T(0, 1))", "N(N(0, 1), N(0, 1))")
Эта проблема является точной копией формального синтаксического анализатора, а не регулярного выражения. При этом может быть решение регулярного выражения.