如何使用正则表达式提取 R 中字符串的不匹配部分?

How can I extract the unmatched portion of a string in R with regular expressions?

我有一个非常混乱的字符串向量。这是一个例子:

library(tidyverse)
library(stringr)
strings <- tibble(
  name = c("lorem 11:07:59 86136-1-sed", 
           "ipsum 14:35:57 S VARNAME-ut",
           "dolor 10:37:53 1513 -2-perspiciatis",
           "sit 10:48:25",
           "amet 13:52:1365293-2-unde",
           "consectetur 11:53:1 16018-2-omnis",
           "adipiscing 11:19 17237-2-iste"
           )
)
strings_out <- strings %>% 
  mutate(heads = str_extract(name, "^.*?\s\d{1,2}:\d{1,2}:\d{1,2}")) %>% 
  mutate(ends = str_replace(name, "^.*?\s\d{1,2}:\d{1,2}:\d{1,2}", ""))
strings_out[,2:3]
#> # A tibble: 7 x 2
#>                 heads                          ends
#>                 <chr>                         <chr>
#> 1      lorem 11:07:59                   86136-1-sed
#> 2      ipsum 14:35:57                  S VARNAME-ut
#> 3      dolor 10:37:53          1513 -2-perspiciatis
#> 4        sit 10:48:25                              
#> 5       amet 13:52:13                  65293-2-unde
#> 6 consectetur 11:53:1                 16018-2-omnis
#> 7                <NA> adipiscing 11:19 17237-2-iste

所以这里我有一些字符串,其中包含一些文本,后跟可能输入正确或可能不正确的时间,然后是更多文本。我只想在时间之后提取字符串的结尾,但是它们没有任何模式似乎与使用 str_extract 的潜在正则表达式很好地对应。我可以轻松匹配字符串的前半部分,如 heads 所示。但是,我发现提取后半部分的唯一方法是使用 str_replace 和空字符串,如 ends.

所示

我试图将我注意到的所有常见错误包括在此列表中:没有关于时间后的连字符、空格或字符串内容的模式,不能保证 space 时间和所需的结束一半之间字符串,乘以缺失数字甚至冒号。

我想做的是能够使用 str_extract 得到接近我用 str_replace 得到的东西。关键区别在于,对于此正则表达式仍然不起作用的错误,str_extract 给我一个易于过滤和手动修复的 NA,但 str_replace 只是复制整个第 7 行中显示的字符串。

我怀疑我可以用一些更 hacky 的方法来做到这一点,比如获取所有 NA 并手动修复 Excel 之类的,但令我惊讶的是我不知道如何return 尽管搜索并尝试了包括 (^)[^] 在内的不同正则表达式,但通常是字符串的不匹配部分。有什么想法吗?

一般来说,您可能想要查看 lookarounds,但您的数据可能需要更多结构才能发挥作用。

这是我在意识到时间后并不总是有 space 之前写的一个简单示例:


library(tidyverse)
library(stringr)
strings <- tibble(
  name = c("lorem 11:07:59 86136-1-sed", 
           "ipsum 14:35:57 S VARNAME-ut",
           "dolor 10:37:53 1513 -2-perspiciatis",
           "sit 10:48:25",
           "amet 13:52:1365293-2-unde",
           "consectetur 11:53:1 16018-2-omnis",
           "adipiscing 11:19 17237-2-iste"
  )
)
strings_out <- strings %>% 
  mutate(heads = str_extract(name, "^.*?\s\d{1,2}:\d{1,2}:\d{1,2}"),
         ends = str_extract(name, "(?<=:\d{1,2} )[\s\S]+$"))

strings_out[c(1,3)]
#> # A tibble: 7 x 2
#>                                  name                 ends
#>                                 <chr>                <chr>
#> 1          lorem 11:07:59 86136-1-sed          86136-1-sed
#> 2         ipsum 14:35:57 S VARNAME-ut         S VARNAME-ut
#> 3 dolor 10:37:53 1513 -2-perspiciatis 1513 -2-perspiciatis
#> 4                        sit 10:48:25                 <NA>
#> 5           amet 13:52:1365293-2-unde                 <NA>
#> 6   consectetur 11:53:1 16018-2-omnis        16018-2-omnis
#> 7       adipiscing 11:19 17237-2-iste         17237-2-iste

这里的问题是像第 5 行这样的行。没有更多的结构,我们无法知道时间是 13:52:13 还是 13:52:1,因为两者都是其他字符串中存在的选项。判断哪个正确不是正则表达式可以解决的问题

你也可以试试这个:

library(tidyverse)
library(stringr)

regex = "^\w+\s\d{2}:\d{2}:*\d{0,2}"

strings %>%
  mutate(head = str_extract(name, regex),
         end = str_replace(name, paste0(regex, "\s?"), ""),
         end = str_replace(end, "^\s*$", NA_character_))

结果:

# A tibble: 7 x 3
                                 name                head                  end
                                <chr>               <chr>                <chr>
1          lorem 11:07:59 86136-1-sed      lorem 11:07:59          86136-1-sed
2         ipsum 14:35:57 S VARNAME-ut      ipsum 14:35:57         S VARNAME-ut
3 dolor 10:37:53 1513 -2-perspiciatis      dolor 10:37:53 1513 -2-perspiciatis
4                        sit 10:48:25        sit 10:48:25                 <NA>
5           amet 13:52:1365293-2-unde       amet 13:52:13         65293-2-unde
6   consectetur 11:53:1 16018-2-omnis consectetur 11:53:1        16018-2-omnis
7       adipiscing 11:19 17237-2-iste    adipiscing 11:19         17237-2-iste

注:

我的解决方案适用于第 5 行,但您必须决定在这种情况下是要提取 13:52:13 还是 13:52:1。这两种情况都可以通过对正则表达式进行简单修改来完成,但正如@Zach 所述,没有自动方法。

您只需多加一行即可:

strings["rx"] <- str_match(strings$name, "\d*:\d*(?::\d+)?(.*)")[,2]
strings

产生

# A tibble: 7 x 2
                                 name                    rx
                                <chr>                 <chr>
1          lorem 11:07:59 86136-1-sed           86136-1-sed
2         ipsum 14:35:57 S VARNAME-ut          S VARNAME-ut
3 dolor 10:37:53 1513 -2-perspiciatis  1513 -2-perspiciatis
4                        sit 10:48:25                      
5           amet 13:52:1365293-2-unde               -2-unde
6   consectetur 11:53:1 16018-2-omnis         16018-2-omnis
7       adipiscing 11:19 17237-2-iste          17237-2-iste