在R中使用正则表达式多次捕获分组

108
在R中,是否可以从正则表达式匹配中提取分组捕获?据我所知,grepgreplregexprgregexprsubgsub都不返回分组捕获。
我需要从编码为键值对的字符串中提取键值对。
\((.*?) :: (0\.[0-9]+)\)

我总是可以使用多个完全匹配的grep,或进行一些非R处理,但我希望可以在R内完成所有操作。是否有函数或包提供此类功能以实现此目的?

9个回答

131

str_match()函数来自于stringr包,可以实现此功能。它返回一个字符矩阵,其中每个匹配组对应一列(还有一个列对应整个匹配):

> s = c("(sometext :: 0.1231313213)", "(moretext :: 0.111222)")
> str_match(s, "\\((.*?) :: (0\\.[0-9]+)\\)")
     [,1]                         [,2]       [,3]          
[1,] "(sometext :: 0.1231313213)" "sometext" "0.1231313213"
[2,] "(moretext :: 0.111222)"     "moretext" "0.111222"    

2
使用 str_match_all() 函数来匹配正则表达式中的所有组。 - smci
我该如何仅打印出捕获组的 [,1] 部分? - nosh
1
不确定您正在寻找什么。捕获的组是第2列和第3列。[,1]是完整匹配。[,2:3]是捕获的组。 - Kent Johnson

67

从你的例子中,gsub可以实现这个功能:

gsub("\\((.*?) :: (0\\.[0-9]+)\\)","\\1 \\2", "(sometext :: 0.1231313213)")
[1] "sometext 0.1231313213"

你需要在引号中双重转义 \s,然后它们才能在正则表达式中起作用。

希望这可以帮到你。


实际上,我需要提取捕获的子字符串并放入数据框中。但是,看了你的答案,我想我可以链接 gsub 和一些 strsplit 来得到我想要的结果,也许是这样的:strsplit(strsplit(gsub(regex, "\1::\2::::", str), "::::")[[1]], "::") - Daniel Dickison
13
好的。 R语言中 gsub 的手册非常需要一个例子,显示您需要使用 '\1' 转义捕获组引用。 - smci

45

尝试使用regmatches()regexec()

regmatches("(sometext :: 0.1231313213)",regexec("\\((.*?) :: (0\\.[0-9]+)\\)","(sometext :: 0.1231313213)"))
[[1]]
[1] "(sometext :: 0.1231313213)" "sometext"                   "0.1231313213"

7
感谢您提供的香草R解决方案,并指出我以前从未见过的“regmatches”。 - Andy
为什么你必须把字符串写两次? - Stefano Borini
1
@StefanoBorini regexec 返回一个列表,其中包含有关匹配位置的信息,因此 regmatches 要求用户提供匹配列表所属的字符串。 - RTbecard
@andy 等你听到strcapture的时候,一定会很惊喜。 - jan-glx

20

gsub()函数可以做到这一点,并且只返回捕获组:

然而,为了使其工作,您必须像在 gsub() 帮助中提到的那样显式选择捕获组之外的元素。

字符向量 'x' 的(...)元素不会被替换,并将原样返回。

因此,如果要选择的文本位于某个字符串的中间,在捕获组之前和之后添加 .* 应该允许您仅返回它。

gsub(".*\\((.*?) :: (0\\.[0-9]+)\\).*","\\1 \\2", "(sometext :: 0.1231313213)") [1] "sometext 0.1231313213"


7

使用utils中的strcapture解决方案:

x <- c("key1 :: 0.01",
       "key2 :: 0.02")
strcapture(pattern = "(.*) :: (0\\.[0-9]+)",
           x = x,
           proto = list(key = character(), value = double()))
#>    key value
#> 1 key1  0.01
#> 2 key2  0.02

1
这是处理此类事情的正确方式。它允许使用 PCRE 并强制您明确指定预期的列类型和名称。 - jan-glx

4

我喜欢Perl兼容的正则表达式,可能其他人也一样...

这里有一个函数,它可以实现Perl兼容的正则表达式,并且与我习惯使用的其他语言中的函数具有相同的功能:

regexpr_perl <- function(expr, str) {
  match <- regexpr(expr, str, perl=T)
  matches <- character(0)
  if (attr(match, 'match.length') >= 0) {
    capture_start <- attr(match, 'capture.start')
    capture_length <- attr(match, 'capture.length')
    total_matches <- 1 + length(capture_start)
    matches <- character(total_matches)
    matches[1] <- substr(str, match, match + attr(match, 'match.length') - 1)
    if (length(capture_start) > 1) {
      for (i in 1:length(capture_start)) {
        matches[i + 1] <- substr(str, capture_start[[i]], capture_start[[i]] + capture_length[[i]] - 1)
      }
    }
  }
  matches
}

4
这就是我解决这个问题的方法。我使用了两个不同的正则表达式来匹配第一和第二个捕获组,并运行了两个gregexpr调用,然后提取出匹配的子字符串:
regex.string <- "(?<=\\().*?(?= :: )"
regex.number <- "(?<= :: )\\d\\.\\d+"

match.string <- gregexpr(regex.string, str, perl=T)[[1]]
match.number <- gregexpr(regex.number, str, perl=T)[[1]]

strings <- mapply(function (start, len) substr(str, start, start+len-1),
                  match.string,
                  attr(match.string, "match.length"))
numbers <- mapply(function (start, len) as.numeric(substr(str, start, start+len-1)),
                  match.number,
                  attr(match.number, "match.length"))

+1 表示代码可以运行。但是,我更喜欢从 R 运行一个快速的 shell 命令,并使用类似于此的 Bash 单行命令 expr "xyx0.0023xyxy" : '[^0-9]*\([.0-9]\+\)' - Aleksandr Levchuk

2

如同 stringr 包中所建议的,可以使用 str_match()str_extract() 来实现此功能。

来自手册的改编:

library(stringr)

strings <- c(" 219 733 8965", "329-293-8753 ", "banana", 
             "239 923 8115 and 842 566 4692",
             "Work: 579-499-7527", "$1000",
             "Home: 543.355.3679")
phone <- "([2-9][0-9]{2})[- .]([0-9]{3})[- .]([0-9]{4})"

提取和组合我们的群组:

str_extract_all(strings, phone, simplify=T)
#      [,1]           [,2]          
# [1,] "219 733 8965" ""            
# [2,] "329-293-8753" ""            
# [3,] ""             ""            
# [4,] "239 923 8115" "842 566 4692"
# [5,] "579-499-7527" ""            
# [6,] ""             ""            
# [7,] "543.355.3679" ""   

使用输出矩阵指示组(我们只关心第二列及之后的列):

str_match_all(strings, phone)
# [[1]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "219 733 8965" "219" "733" "8965"
# 
# [[2]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "329-293-8753" "329" "293" "8753"
# 
# [[3]]
#      [,1] [,2] [,3] [,4]
# 
# [[4]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "239 923 8115" "239" "923" "8115"
# [2,] "842 566 4692" "842" "566" "4692"
# 
# [[5]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "579-499-7527" "579" "499" "7527"
# 
# [[6]]
#      [,1] [,2] [,3] [,4]
# 
# [[7]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "543.355.3679" "543" "355" "3679"

842 566 4692是什么意思? - Ferroao
感谢您发现这个遗漏。已经使用 _all 后缀来纠正相关的 stringr 函数。 - Megatron

1
这可以通过使用unglue软件包来实现,以下是所选答案的示例:
# install.packages("unglue")
library(unglue)

s <- c("(sometext :: 0.1231313213)", "(moretext :: 0.111222)")
unglue_data(s, "({x} :: {y})")
#>          x            y
#> 1 sometext 0.1231313213
#> 2 moretext     0.111222

从数据框开始
df <- data.frame(col = s)
unglue_unnest(df, col, "({x} :: {y})",remove = FALSE)
#>                          col        x            y
#> 1 (sometext :: 0.1231313213) sometext 0.1231313213
#> 2     (moretext :: 0.111222) moretext     0.111222

你可以从unglue模式中获取原始正则表达式,可选择使用命名捕获:
unglue_regex("({x} :: {y})")
#>             ({x} :: {y}) 
#> "^\\((.*?) :: (.*?)\\)$"

unglue_regex("({x} :: {y})",named_capture = TRUE)
#>                     ({x} :: {y}) 
#> "^\\((?<x>.*?) :: (?<y>.*?)\\)$"

更多信息: https://github.com/moodymudskipper/unglue/blob/master/README.md


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