【问题标题】:Splitting a string with nested parentheses at only the top level where "level" is determined by the parentheses仅在“级别”由括号确定的顶层拆分带有嵌套括号的字符串
【发布时间】:2021-03-21 22:03:23
【问题描述】:

我正在尝试创建一个正则表达式,它允许我仅在中间逗号上拆分下面的字符串。

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_2str_3str_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 来减少代码的依赖项数量。任何帮助深表感谢。这也可能无法通过正则表达式解决,但需要一种编程方法,可能通过this Java 问题中所建议的递归来解决。

【问题讨论】:

  • 这个问题是正式解析器的死铃声,而不是正则表达式。话虽如此,可能有一个正则表达式解决方案。
  • strsplit(substring(str_2, 3, nchar(str_2)-1), ",(?![^()]*\\))", perl=TRUE)
  • 最好使用词法分析工具(lex)和语法分析器(yacc)。然而,有一些正则表达式可以扫描嵌套结构。也可以使用任何正则表达式引擎进行多次解析:1. 为每个括号注释嵌套级别,2. 扫描相同级别的左括号和右括号,3. 删除嵌套注释。详情请见twiki.org/cgi-bin/view/Blog/BlogEntry201109x3

标签: r regex string recursion pcre


【解决方案1】:

如果您的字符向量采用您显示的格式,您可以使用单个 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 组(需要递归才能正常工作):(,然后是除 () 之外的任何零个或多个字符,然后是第 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)"

【讨论】:

  • 也感谢您对表达式组件的精彩解释。非常感谢。
【解决方案2】:

s 定义为字符串的字符向量。我们计算左括号的累积数减去右括号的累积数,并将差值为 0 的任何逗号替换为分号,然后将其拆分。

为此,我们使用gsubfn,它类似于gsub,除了替换不必是字符串,而是可以是原型对象。 proto 对象的pre 方法在每个字符串的开头运行,fun 方法对传递给gsubfn 的模式的每个匹配项运行。下面定义的pre 方法将lev 设置为0,其中lev 将保存上面讨论的累积差异。 fun 在每次匹配左括号、右括号或逗号时运行,每次匹配到:

  • 左括号将递增lev
  • 右括号将递减lev
  • 如果 lev == 0,它将发出分号替换逗号

使用sub 删除输入s 开头和结尾的垃圾,运行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)"   

【讨论】:

    【解决方案3】:

    如果我们认为结构保持不变,那么我们可以这样做:

    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))")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-14
      相关资源
      最近更新 更多