如何仅删除单个括号并保留成对的括号

5

大家好,我是你们亲爱的老师/同行R用户。

最近我开始认真学习正则表达式,在学习过程中遇到了一个情况:我们只想保留成对的括号(),而忽略那些不成对的。以下是我的样例数据

structure(list(t1 = c("Book (Pg 1)", "(Website) Online)", "Journal: 2018)", 
"Book1 (pg 2) book 3 (pg4)  something)")), class = "data.frame", row.names = c(NA, 
-4L))

我期望的输出结果如下:

structure(list(t1 = c("Book (Pg 1)", "(Website) Online", "Journal: 2018", 
"Book1 (pg 2) book 3 (pg4)  something")), class = "data.frame", row.names = c(NA, 
-4L))

我自己用以下代码成功实现了,但我想肯定有更有效的方法。事实上,我想学习其他实现类似结果的方法:

test$t2 <- gsub("([(]?.*[)]?\\s+[^(]\\w+)[)]|([(].*[)])", "\\1\\2", test$t1)
test

                                     t1                                   t2
1                           Book (Pg 1)                          Book (Pg 1)
2                     (Website) Online)                     (Website) Online
3                        Journal: 2018)                        Journal: 2018
4 Book1 (pg 2) book 3 (pg4)  something) Book1 (pg 2) book 3 (pg4)  something

我的正则表达式出现问题,当我交换 | 操作符中RHSLHS的位置时,无法得到期望的结果,这让我很好奇。我希望您能简要解释如何解决这些问题。非常感谢您的帮助。
3个回答

4
相当直接明了:
\([^()]*\)(*SKIP)(*FAIL)|[()]+

使用perl = T参数。


简单解释一下:

\([^()]*\)(*SKIP)(*FAIL) # match any balanced parenthesis construct and let the engine skip it
|                        # or
[()]+                    # match single parentheses

查看回溯控制动词,并在regex101.com上查看演示


3

您可以使用

> gsub("\\([^()]*\\)(*SKIP)(*F)|[()]", "", df$t1, perl=TRUE)
[1] "Book (Pg 1)"                          "(Website) Online"                    
[3] "Journal: 2018"                        "Book1 (pg 2) book 3 (pg4)  something"

查看R在线演示正则表达式演示

详细说明

  • \([^()]*\)(*SKIP)(*F) - 匹配一个(字符,后跟零个或多个非()字符,然后是一个)字符,匹配的文本被丢弃,下一次匹配从失败位置开始搜索
  • | - 或
  • [()] - 匹配一个()字符。

如果您需要跳过平衡的、嵌套的括号,可以使用

gsub("(\\((?:[^()]++|(?-1))*\\))(*SKIP)(*F)|[()]", "", df$t1, perl=TRUE)

在这里,(\((?:[^()]++|(?-1))*\))(*SKIP)(*F)匹配并跳过任何嵌套括号之间的子字符串(例如(aa (bb(c)x)x)),|[()]匹配其他上下文中的任何()
请参见此正则表达式演示(*SKIP)(*F)(=(*FAIL))PCRE动词的含义为:
- (*SKIP) - 引擎前进到与在模式中遇到(*SKIP)相应的字符串位置,并从该位置开始新的匹配尝试,或者引擎跳转到匹配(*SKIP)的字符串位置,可能节省了大量无效的匹配尝试。 - (*F) - 向正则表达式引擎发出失败信号,如果适用则触发回溯(由于|选择运算符,这里会触发回溯)。请注意,(*F)(?!)相同,即如果右侧有任何内容,则失败。

非常感谢您的回答。我有点难以理解这里的(*SKIP)(*F)是什么意思。如果您能向我解释一下,我将不胜感激。 - Anoushiravan R
@Jan非常感谢您的回复和答案。是的,您说得对,我现在会检查它们。 - Anoushiravan R
1
@AnoushiravanR 我添加了简要说明。实际上,我很少在答案中详细解释PCRE动词,我总是认为简短的模式说明就足够了。如果简要信息不清楚,请告诉我。 - Wiktor Stribiżew
非常感谢您,亲爱的@WiktorStribiżew先生,您是正确的,我只需要花时间学习它们,因为这些概念对我来说是新的,但这是一个让我学习更高级东西的绝佳机会,感谢您。如果我有问题,我会告诉您,我非常感激您的帮助。 - Anoushiravan R
1
@AnoushiravanR 你好,我开始在Youtube上上传一些正则表达式视频,如果你想学习更多关于正则表达式的知识,可以随时查看。由于我是一个Youtubing的新手,我会非常感激任何建议。 - Wiktor Stribiżew
你好,亲爱的Wiktor。非常感谢您告知我有关正则表达式教程的信息。我一定会非常感兴趣地查看它们,并在此提前感谢您与他人分享您的知识。 - Anoushiravan R

1

(\\([^)]*\\))*|\\)* 正则表达式可能解决问题

可以在这里看到正则表达式的解释说明

  • 2个备选项匹配。第一个被捕获,第二个没有被捕获。因此第一个被包含在()中,第二个没有
  • \\( 匹配文字 (
  • [^)] 匹配除了文字 ) 以外的所有内容
  • * 连续匹配前面的标记。由于没有匹配 ),它一直到第一对 ) 的结尾
  • \\) 匹配 )
  • 括号组后面的 * 匹配任意数量的这些
  • 第二个备选项匹配 )
  • 所有匹配都用第一个捕获组 \\1 替换
  • 因此,您想要的结果
test <- structure(list(t1 = c("Book (Pg 1)", "(Website) Online)", "Journal: 2018)", 
                              "Book1 (pg 2) book 3 (pg4)  something)")), class = "data.frame", row.names = c(NA, 
                                                                                                             -4L))
test
#>                                      t1
#> 1                           Book (Pg 1)
#> 2                     (Website) Online)
#> 3                        Journal: 2018)
#> 4 Book1 (pg 2) book 3 (pg4)  something)

gsub('(\\([^)]*\\))*|\\)*', '\\1', test$t1)

#> [1] "Book (Pg 1)"                         
#> [2] "(Website) Online"                    
#> [3] "Journal: 2018"                       
#> [4] "Book1 (pg 2) book 3 (pg4)  something"

这段内容是由reprex包(v2.0.0)于2021年07月04日创建的。


1
请问您能否简单解释一下您的正则表达式大师Anil? - Anoushiravan R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接