【问题标题】:Optimizing a regex in R for substring extraction优化 R 中的正则表达式以提取子字符串
【发布时间】:2021-12-28 15:51:12
【问题描述】:

我有一个关于先前答案的后续问题,可以在这里找到:Split uneven string in R - variable substring and delimiters

总之,我想在一个遵循这种模式的字符串中提取粗体文本:

sp|Q2UVX4|CO3_BOVIN **Complement C3** OS=Bos taurus OX=9913 GN=**C3** PE=1 SV=2

这是 Martin Gal 提供的部分答案:

protein_name = ifelse(str_detect(string, ".*_BOVIN\\s(.*?)\\sOS=.*"), 
                      str_replace(string, ".*_BOVIN\\s(.*?)\\sOS=.*", "\\1"),
                      NA_character_),

他的回答非常好,但有时我有多种物种(例如:BOVIN 和 HUMAN),所以我想让代码更灵活一点。我尝试只使用空格(\\s) 和带有空格([A-Z]\\s) 的大写字母,但第一个失败,第二个对于某些字符串不准确。然后我将 Martin 的方法与以大写字母结尾的字符串混合,旨在选择整个第一个块作为分隔符(例如:sp|Q2UVX4|CO3_BOVIN)。

到这里:

protein_name = ifelse(str_detect(string, "[a-z]{2}\\|(.*?)[A-Z]\\s(.*?)\\sOS=.*"), 
                      str_replace(string, "[a-z]{2}\\|(.*?)[A-Z]\\s(.*?)\\sOS=.*", "\\2")
  • 在这种情况下,选择两种模式之间的所有内容的最佳方法是什么?这两种模式是“sp”和大写字母后跟一个空格。
  • 我使用了(.*?),这是最好的方法吗?

【问题讨论】:

  • 您所说的“最佳”是什么意思? .*? 是最简单的,但不是最快的。它不匹配换行符。
  • 您不能将string 包含在问题中
  • 还包括所需的输出,因为在同一个字符串中有多个 BOVIN
  • 有点不清楚你在做什么(在阅读了这个问题之后)。看起来你只需要str_match(string, '[a-z]{2}\\|[|\\w]*[A-Z]\\s+(.*?)\\s+OS=')[,2](见demodemo)。
  • 谢谢你们两位的cmets。我最好的意思是确保我使用的正则表达式对我的情况是精确的,并且不允许“误报”。例如,如果我只使用.*,我可能会冒匹配遵循相同模式的更长字符串的风险,对吧?另外,我需要 .*? 周围的括号吗? @Wiktor,您链接的 regex101 太棒了。太感谢了。但是我还是看不懂[|\\w]*。你介意解释一下吗?

标签: r regex string


【解决方案1】:

您的“最佳”模式始终是满足您所有要求的模式。所以,总是从定义要求开始:比赛应该以...开头,以下字符可以出现在这里,那里...并且比赛应该以...结束

因此,在您的情况下,您似乎放弃了中间检查而只使用

library(stringr)
str_match(string, '[a-z]{2}\\|[|\\w]*[A-Z]\\s+(.*?)\\s+OS=')[,2]

由于stringr::str_match 保留所有捕获,因此当您必须匹配复杂上下文中的某些模式时,它会非常有用。 [,2] 访问第 1 组的内容。

正则表达式匹配:

  • [a-z]{2} - 两个小写 ASCII 字母(这里,性能没有问题,当你告诉正则表达式匹配单个字符重复 X 次时,这非常有效)
  • \| - 一个 | 字符(同样,这很好,文字匹配效率很高)
  • [|\w]* - 零个或多个 | 或单词字符(这很容易回溯,因为下一个模式匹配一​​个大写字母,这也是一个单词字符,但在这里,我们需要这个回溯)
  • [A-Z] - 一个大写的 ASCII 字母
  • \s+ - 一个或多个空格字符
  • (.*?) - 第 1 组:除换行符之外的零个或多个字符尽可能少(这是这里最消耗资源的模式,因为如果后续模式不匹配,它将在 char 之后扩展 char;而且,它确实默认不匹配换行符,如果有换行符需要((?s:.*?)))
  • \s+ - 一个或多个空格字符
  • OS= - OS= 子字符串。

请参阅regex demo。见the R demo:

string <- 'sp|Q2UVX4|CO3_BOVIN Complement C3 OS=Bos taurus OX=9913 GN=C3 PE=1 SV=2'
library(stringr)
str_match(string, '[a-z]{2}\\|[|\\w]*[A-Z]\\s+(.*?)\\s+OS=')[,2]

输出:

# => [1] "Complement C3"

如果您需要优化.*? 模式,您需要阅读并学习使用unroll-the-loop approach。 Tl;博士:

[a-z]{2}\|[|\w]*[A-Z]\s+(\S*(?:\s(?!\s*OS=)\S*)*)\s+OS=

this regex demo

.*? 被转换为 \S*(?:\s(?!\s*OS=)\S*)*(参见后续模式被“缝入”这个构造),它匹配

  • \S* - 零个或多个非空白字符
  • (?:\s(?!\s*OS=)\S*)* - 零个或多个任何空白序列,不紧跟零个或多个空格和OS=,然后是零个或多个非空白字符。

【讨论】:

  • 非常感谢@Wiktor 做出如此彻底的回应! “展开循环”方法令人印象深刻。处理时间似乎减少了约 40%。我只能想象对大量字符串的影响。哇!真的很抱歉,但[|\w]* 仍然不是 100% 清楚。为什么你不必在| 之前使用`\\`?另外,为了仔细检查,一旦找到一个空字符,[|\w]* 是否停止,然后[A-Z]\\s+ 允许正则表达式继续前进?
  • @LuizGustavo 1) 只有特殊字符才能在正则表达式模式中转义。字符类中的| 不是特殊字符,因此无需转义。 2)[|\w]*匹配字母、数字、_|字符,所以它不匹配空格(如果你的意思是“空字符”是一个空格字符,那么是的,它会“停止一次空字符找到”)。
【解决方案2】:

可以这样解决:

str_extract_all(string, "(?<=(?:BOVIN|HUMAN) )(.*?)(?= OS).*?GN=(\\w+)") %>%
   map_df(~read.table(text=str_replace(.,"OS.*GN", ""), sep="=",
             col.names = c('protein_name', 'gene')), .id='grp')
   grp                                                                protein_name   gene
1    1                                                              Complement C3      C3
2    1                                                                  C3-beta-c      C3
3    1                                                                  C3-beta-c      C3
4    2                                                                Haptoglobin      HP
5    2                                                                Haptoglobin      HP
6    2                                                                Haptoglobin      HP
7    3                                                     Anion exchange protein  SLC4A7
8    4                                        Isoform V3 of Versican core protein    VCAN
9    4                                        Isoform V2 of Versican core protein    VCAN
10   4                                                      Versican core protein    VCAN
11   5 Keratin 10 (Epidermolytic hyperkeratosis; keratosis palmaris et plantaris)   KRT10
12   5                                            Keratin, type I cytoskeletal 10   KRT10

您还可以使用以下内容。请注意,as_tibble 不是必需的。用它来获得漂亮的结果

unlist(strsplit(string, "\\w{2}=\\w+\\K;", perl = TRUE))%>%
   sub(".*?(?:BOVIN|HUMAN) (.*?)(?= OS).*?GN=(\\w+).*|.*",  "\\1:\\2", ., perl = TRUE) %>%
   read.table(text=., sep=":") %>%
   as_tibble()

 A tibble: 14 x 2
   V1                                                                           V2      
   <chr>                                                                        <chr>   
 1 "Complement C3"                                                              "C3"    
 2 "C3-beta-c"                                                                  "C3"    
 3 "C3-beta-c"                                                                  "C3"    
 4 ""                                                                           ""      
 5 "Haptoglobin"                                                                "HP"    
 6 "Haptoglobin"                                                                "HP"    
 7 "Haptoglobin"                                                                "HP"    
 8 ""                                                                           ""      
 9 "Anion exchange protein"                                                     "SLC4A7"
10 "Isoform V3 of Versican core protein"                                        "VCAN"  
11 "Isoform V2 of Versican core protein"                                        "VCAN"  
12 "Versican core protein"                                                      "VCAN"  
13 "Keratin 10 (Epidermolytic hyperkeratosis; keratosis palmaris et plantaris)" "KRT10" 
14 "Keratin, type I cytoskeletal 10"                                            "KRT10" 

【讨论】:

  • 感谢您的回答!很抱歉在发布我的问题时不清楚,但 BOVIN 和 HUMAN 只是两个例子。如果我想让它灵活适应任何完全大写的单词,我应该用[[:upper:]\\w]替换BOVIN|HUMAN吗?
  • @LuizGustavo \\b[A-Z]+\\b
  • 对不起@Onyambu,但我在这里尝试过,但没有成功。我可能在做一些愚蠢的事情。它应该看起来像这样:".*?(?:\\b[A-Z]+\\b) (.*?)(?= OS).*?GN=(\\w+).*|.*" 感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-18
  • 1970-01-01
相关资源
最近更新 更多