仅在 "level" 由括号确定的顶层拆分带有嵌套括号的字符串
Splitting a string with nested parentheses at only the top level where "level" is determined by the parentheses
我正在尝试创建一个正则表达式,它允许我仅在中央逗号处拆分下面的字符串。
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)"
如果可能的话,我想继续使用 base R 以减少代码的依赖项数量。任何帮助深表感谢。这也可能无法通过正则表达式解决,但需要一种可能通过递归的编程方法,如 this Java 问题中的建议。
如果您的字符向量采用您显示的格式,您可以使用单个 PCRE 正则表达式实现所需的内容:
(?:\G(?!^)\s*,\s*|^N\()\K(?:\d+|\w+(\([^()]*(?:(?1)[^()]*)*\)))(?=\s*,|\)$)
参见regex demo。 详情
(?:\G(?!^)\s*,\s*|^N\()
- 上一个成功匹配的结尾 (\G(?!^)
),然后是用零个或多个空白字符括起来的逗号 (\s*,\s*
) 或 N(
字符串在字符串的开头 (^N\(
)
\K
- 一个匹配重置运算符,它会丢弃目前匹配内存缓冲区中所有匹配的文本
(?:
- 非捕获组的开始
\d+
- 一个或多个数字
|
- 或
\w+
- 一个或多个单词字符
(\([^()]*(?:(?1)[^()]*)*\))
- 第 1 组(需要递归才能正常工作):a (
,然后是 (
和 )
以外的任何零个或多个字符,然后零次或多次出现第 1 组模式(递归),然后零次或多次 (
和 )
以外的字符,然后是 )
字符
)
- 非捕获组结束
(?=\s*,|\)$)
- 紧随其后的是零个或多个空格,然后是逗号或 )
字符串末尾的字符。
参见 regex demo:
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
,除了替换不必是字符串,但可以是原型对象。每个字符串开头的 proto 对象 运行s 的 pre
方法和传递给 gsubfn
的模式的每个匹配的 fun
方法 运行s .下面定义的 pre
方法将 lev
设置为 0,其中 lev
将保存上面讨论的累积差异。 fun
在每次匹配左括号、右括号或逗号时是 运行,每次我们匹配到:
- 左括号会递增
lev
- 右括号会递减
lev
- comma 如果 lev == 0
,它将发出分号替换逗号
使用sub
、运行 gsubfn
删除输入s
开头和结尾的垃圾,然后在分号处拆分结果。最后我们将结果简化为一个数据框。此处输出字符向量的长度均为 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"{}", 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))")
我正在尝试创建一个正则表达式,它允许我仅在中央逗号处拆分下面的字符串。
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)"
如果可能的话,我想继续使用 base R 以减少代码的依赖项数量。任何帮助深表感谢。这也可能无法通过正则表达式解决,但需要一种可能通过递归的编程方法,如 this Java 问题中的建议。
如果您的字符向量采用您显示的格式,您可以使用单个 PCRE 正则表达式实现所需的内容:
(?:\G(?!^)\s*,\s*|^N\()\K(?:\d+|\w+(\([^()]*(?:(?1)[^()]*)*\)))(?=\s*,|\)$)
参见regex demo。 详情
(?:\G(?!^)\s*,\s*|^N\()
- 上一个成功匹配的结尾 (\G(?!^)
),然后是用零个或多个空白字符括起来的逗号 (\s*,\s*
) 或N(
字符串在字符串的开头 (^N\(
)\K
- 一个匹配重置运算符,它会丢弃目前匹配内存缓冲区中所有匹配的文本(?:
- 非捕获组的开始\d+
- 一个或多个数字|
- 或\w+
- 一个或多个单词字符(\([^()]*(?:(?1)[^()]*)*\))
- 第 1 组(需要递归才能正常工作):a(
,然后是(
和)
以外的任何零个或多个字符,然后零次或多次出现第 1 组模式(递归),然后零次或多次(
和)
以外的字符,然后是)
字符
)
- 非捕获组结束(?=\s*,|\)$)
- 紧随其后的是零个或多个空格,然后是逗号或)
字符串末尾的字符。
参见 regex demo:
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
,除了替换不必是字符串,但可以是原型对象。每个字符串开头的 proto 对象 运行s 的 pre
方法和传递给 gsubfn
的模式的每个匹配的 fun
方法 运行s .下面定义的 pre
方法将 lev
设置为 0,其中 lev
将保存上面讨论的累积差异。 fun
在每次匹配左括号、右括号或逗号时是 运行,每次我们匹配到:
- 左括号会递增
lev
- 右括号会递减
lev
- comma 如果 lev == 0 ,它将发出分号替换逗号
使用sub
、运行 gsubfn
删除输入s
开头和结尾的垃圾,然后在分号处拆分结果。最后我们将结果简化为一个数据框。此处输出字符向量的长度均为 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"{}", 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))")