【问题标题】:Regexes works on their own, but not when used together in strsplit正则表达式单独工作,但在 strsplit 中一起使用时不能
【发布时间】:2017-06-11 06:31:59
【问题描述】:

我正在尝试使用 strsplit 和 perl 正则表达式在 R 中拆分字符串。该字符串由各种由句点或连字符分隔的字母数字标记组成,例如"WXYZ-AB-A4K7-01A-13B-J29Q-10"。我要拆分字符串:

  • 连字符出现的地方。
  • 句号出现的地方。
  • 标记的第二个和第三个字符之间正好是 3 个字符长,由 2 个数字后跟 1 个大写字母组成,例如 "01A" 产生 ["01", "A"](但 "012A""B1A""0A1" , 和 "01A2" 没有拆分)。

例如,"WXYZ-AB-A4K7-01A-13B-J29Q-10" 应该产生 ["WXYZ", "AB", "01", "A", "13", "B", "J29Q", "10"]

我当前的正则表达式是((?<=[-.]\\d{2})(?=[A-Z][-.]))|[.-],它在this online regex tester 中完美运行。

此外,替代方案的两个部分 ((?<=[-.]\\d{2})(?=[A-Z][-.]))[.-] 在单独使用时都用于按照 R 中的预期拆分字符串:

#correctly splits on periods and hyphens
strsplit("WXYZ-AB-A4K7-01A-13B-J29Q-10", "[.-]", perl=T)
[[1]]
[1] "WXYZ" "AB"   "A4K7" "01A"  "13B"  "J29Q" "10"

#correctly splits tokens where a letter follows two digits
strsplit("WXYZ-AB-A4K7-01A-13B-J29Q-10", "((?<=[-.]\\d{2})(?=[A-Z][-.]))", perl=T)
[[1]]
[1] "WXYZ-AB-A4K7-01" "A-13"            "B-J29Q-10"

但是当我尝试使用替代方法组合它们时,第二个正则表达式停止工作,并且字符串仅在句点和连字符上拆分:

#only second alternative is used
strsplit("WXYZ-AB-A4K7-01A-13B-J29Q-10", "((?<=[-.]\\d{2})(?=[A-Z][-.]))|[.-]", perl=T)
[[1]]
[1] "WXYZ" "AB"   "A4K7" "01A"  "13B"  "J29Q" "10"

为什么会这样?是我的正则表达式有问题,还是strsplit 有问题?我怎样才能达到预期的行为?

期望的输出:

## [[1]]
## [1] "WXYZ" "AB"   "A4K7" "01"   "A"    "13"   "B"    "J29Q" "10"

【问题讨论】:

  • 为澄清起见,2285C 是否会拆分为2285C?如果没有,我需要编辑我的答案。
  • @RichScriven 不,它不会,如果令牌完全匹配 \d\d[A-Z] 并且长度为 3,则它们只能在数字字母边界处拆分。
  • @RichScriven,只是加入边界? "[-.]|(?&lt;=\\b[0-9]{2})(?=[A-Z]\\b)"
  • 我投了反对票,因为这篇文章不清楚你的目标是什么。这导致人们浪费时间帮助正确解决错误的问题。如果你说得清楚,我会改变这个投票。最简单的方法是显示所需的输出。通常显示所需的输出会使帖子更清晰 10 倍。
  • @ApproachingDarknessFish 现在做还为时不晚。这使您的帖子对搜索类似问题的其他人有用。

标签: r regex pcre regex-lookarounds strsplit


【解决方案1】:

您可以使用 消费 版本的正向前瞻(匹配重置运算符 \K)以确保 strsplit 在 R 中正常工作,并避免在正面的。

"(?<![^.-])\\d{2}\\K(?=[A-Z](?:[.-]|$))|[.-]"

查看R demo online(和regex demo here)。

strsplit("XYZ-02-01C-33D-2285", "(?<![^.-])\\d{2}\\K(?=[A-Z](?:[.-]|$))|[.-]", perl=TRUE)
## => [[1]]
##    [1] "XYZ"  "02"   "01"   "C"    "33"   "D"    "2285"

strsplit("WXYZ-AB-A4K7-01A-13B-J29Q-10", "(?<![^.-])\\d{2}\\K(?=[A-Z](?:[.-]|$))|[.-]", perl=TRUE)
## => [[1]]
##    [1] "WXYZ" "AB"   "A4K7" "01"   "A"    "13"   "B"    "J29Q" "10" 

这里,模式匹配:

  • (?&lt;![^.-])\d{2}\K(?=[A-Z](?:[.-]|$)) - 一系列:
    • (?&lt;![^.-])\d{2} - 2 位数字 (\d{2}),前面没有除 .- 以外的字符(即前面有 .- 或字符串开头,这是一个常见的技巧以避免在环视内交替)
    • \K - 匹配重置运算符,使正则表达式引擎丢弃到目前为止匹配的文本并继续匹配后续子模式(如果有)
  • | - 或
  • [.-] - 匹配 .-

【讨论】:

    【解决方案2】:

    感谢 Rich Scriven 和 Jota,我能够解决这个问题。每次strsplit 找到匹配项时,它都会删除匹配项及其左侧的所有内容,然后再查找下一个匹配项。这意味着当lookbehind 与前一个匹配重叠时,依赖lookbehinds 的正则表达式可能无法按预期运行。在我的例子中,标记之间的连字符在匹配时被删除,这意味着第二个正则表达式无法使用它们来检测标记的开头:

    #first match found
    "WXYZ-AB-A4K7-01A-13B-J29Q-10"
         ^
    
    #match + left removed
    "AB-A4K7-01A-13B-J29Q-10"
    
    #further matches found and removed
    "01A-13B-J29Q-10"
    
    #second regex fails to match because of missing hyphen in lookbehind:
    #((?<=[-.]\\d{2})(?=[A-Z][-.]))
    # ^^^^^^^^
    "01A-13B-J29Q-10"
    
    #algorithm continues
    "13B-J29Q-10"
    

    根据 Jota 的建议,通过用 boundary anchor 替换 [.-] 类以在后视中检测令牌的边缘来解决此问题:

    > strsplit("WXYZ-AB-A4K7-01A-13B-J29Q-10", "[-.]|(?<=\\b\\d{2})(?=[A-Z]\\b)", perl=T)
    [[1]]
    [1] "WXYZ" "AB"   "A4K7" "01"   "A"    "13"   "B"    "J29Q" "10"
    

    【讨论】:

      【解决方案3】:

      另一种避免您不得不考虑strsplit 算法如何工作的替代方法是,使用带有gsub 的原始正则表达式在所有正确的位置插入一个简单的拆分字符,然后使用strsplit 来做直接分裂。

      strsplit(
          gsub("((?<=[-.]\\d{2})(?=[A-Z][-.]))|[.-]", "-", x, perl = TRUE),
          "-", 
          fixed = TRUE)
      #[[1]]
      #[1] "XYZ"  "02"   "01"   "C"    "33"   "D"    "2285"
      

      当然,RichScriven 的回答和 Wiktor Stribiżew 的评论可能更好,因为它们只有一个函数调用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-24
        • 1970-01-01
        相关资源
        最近更新 更多