【问题标题】:Split on first/nth occurrence of delimiter在第一次/第 n 次出现分隔符时拆分
【发布时间】:2014-10-10 14:10:54
【问题描述】:

我正在尝试一些我认为很容易的事情。我正在寻找一个单一的正则表达式解决方案(尽管为了完整性而欢迎其他人)。我想拆分 n 次出现的分隔符。

这是一些数据:

x <- "I like_to see_how_too"
pat <- "_"

期望的结果

假设我想在第一次出现 _ 时拆分:

[1] "I like"  "to see_how_too"

假设我想在第二次出现 _ 时拆分:

[1] "I like_to see"   "how_too"

理想情况下,如果解决方案是一个正则表达式,则可以推广到第 n 次出现;该解决方案将使用带有单个正则表达式的strsplit

这是一个不适合我使用strsplit 的单个正则表达式参数的解决方案

x <- "I like_to see_how_too"
y <- "_"
n <- 1
loc <- gregexpr("_", x)[[1]][n]

c(substr(x, 1, loc-1), substr(x, loc + 1, nchar(x)))

【问题讨论】:

  • 您能否编写一个函数,将基于用户定义的n 的正则表达式模式粘贴在一起?例如,如果n &lt;- 3,那么您可以将(任意)正则表达式粘贴在一起作为paste0("[a-z]{", n, "}")
  • @hwnd - 我认为这可能适合您的答案
  • 哦,那么……继续!哈哈
  • @RichardScriven 更新了,好主意。

标签: regex r


【解决方案1】:

这是另一个使用gsubfn 包和一些正则表达式的解决方案。要更改 nth 出现的分隔符,您可以简单地交换放在范围量词内的数字 - {n}

library(gsubfn)
x <- 'I like_to see_how_too'
strapply(x, '((?:[^_]*_){1})(.*)', c, simplify =~ sub('_$', '', x))
# [1] "I like"  "to see_how_too"

如果您希望第 n 次出现是用户定义的,您可以使用以下内容:

n <- 2
re <- paste0('((?:[^_]*_){',n,'})(.*)')
strapply(x, re, c, simplify =~ sub('_$', '', x))
# [1] "I like_to see" "how_too" 

【讨论】:

  • 请...不要使用 (?:[^_]*(?:_)?){3} 这样的构造来匹配 token separator token separator token 形式。这是灾难性回溯regex101.com/r/rH6sM0/1 的最常见原因。请改用(?:[^_]*_){3}(.*)
  • 实现您提供的正则表达式的问题是_ 分隔符将包含在第一组中,但我编辑并修复了它以及调整后的正则表达式。
【解决方案2】:

无解

由于 R 使用 PCRE,您可以使用 \K 从主匹配结果中删除与 \K 之前的模式匹配的所有内容。

下面是在第三个_分割字符串的正则表达式

^[^_]*(?:_[^_]*){2}\K_

如果您想在第 n 次出现 _ 时进行拆分,只需将 2 更改为 (n - 1)。

Demo on regex101

这就是计划。但是,strsplit 似乎有不同的想法。

实际执行

Demo on ideone.com

x <- "I like_to see_how_too but_it_seems to_be_impossible"
strsplit(x, "^[^_]*(?:_[^_]*)\\K_", perl=TRUE)
strsplit(x,  "^[^_]*(?:_[^_]*){1}\\K_", perl=TRUE)
strsplit(x,  "^[^_]*(?:_[^_]*){0}\\K_", perl=TRUE)

# strsplit(x, "^[^_]*(?:_[^_]*)\\K_", perl=TRUE)
# [[1]]
# [1] "I like_to see" "how_too but"   "it_seems to"   "be_impossible"

# strsplit(x,  "^[^_]*(?:_[^_]*){1}\\K_", perl=TRUE)
# [[1]]
# [1] "I like_to see" "how_too but"   "it_seems to"   "be_impossible"

# strsplit(x,  "^[^_]*(?:_[^_]*){0}\\K_", perl=TRUE)
# [[1]]
# [1] "I like"     "to see"     "how"        "too but"    "it"        
# [6] "seems to"   "be"         "impossible" 

它仍然无法处理更强的断言\A

strsplit(x,  "\\A[^_]*(?:_[^_]*){0}\\K_", perl=TRUE)
# [[1]]
# [1] "I like"     "to see"     "how"        "too but"    "it"        
# [6] "seems to"   "be"         "impossible"

解释?

此行为暗示strsplit 找到第一个匹配项,执行子字符串以提取第一个标记和剩余部分,并在剩余部分中找到下一个匹配项。

这会从之前的匹配项中删除所有状态,并在尝试匹配剩余部分的正则表达式时为我们留下一个干净的状态。这使得在第一次匹配时停止strsplit 函数并同时完成任务的任务变得不可能。 strsplit 中甚至没有一个参数来限制拆分的数量。

【讨论】:

  • 虽然使用{0},但我无法在第一次出现时将其拆分
  • @TylerRinker:R 似乎在第一次匹配后对字符串的其余部分进行了子串化,并尝试再次匹配正则表达式,而不是继续匹配。这会导致 ^ 被识别为字符串中间的字符串开头。
  • @TylerRinker:问题的演示:ideone.com/lfpKCL 实际上,由于它处理拆分的方式,此解决方案在 R 中根本不起作用。
  • @TylerRinker:我已经得出结论,strsplit 的解决方案是不可能的。 绝对没有任何东西可供您使用,可以区分初始输入和拆分后输入的剩余部分。
  • @TylerRinker 这可以使用类似于strsplit的不同方法来完成,我今天下午回家时会提供答案。
【解决方案3】:

而不是拆分,您确实匹配以获取拆分字符串。

试试这个正则表达式:

^((?:[^_]*_){1}[^_]*)_(.*)$

1 替换为n-1,在nth 出现下划线时尝试拆分。

RegEx Demo

更新:似乎R 也支持PCRE,在这种情况下,您也可以使用此PCRE 正则表达式来执行split

^((?:[^_]*_){1}[^_]*)(*SKIP)(*F)|_

1 替换为n-1,在nth 出现下划线时进行拆分。

  • (*FAIL) 表现得像一个失败的否定断言,是 (?!) 的同义词
  • (*SKIP) 定义了一个点,当子模式稍后失败时,正则表达式引擎不允许回溯。
  • (*SKIP)(*FAIL) 一起提供了一个很好的限制替代方案,您不能在上面的正则表达式中使用可变长度的lookbehind。

RegEx Demo2

x <- "I like_to see_how_too"

strsplit(x,  "^((?:[^_]*_){0}[^_]*)(*SKIP)(*F)|_", perl=TRUE)
strsplit(x,  "^((?:[^_]*_){1}[^_]*)(*SKIP)(*F)|_", perl=TRUE)

## > strsplit(x,  "^((?:[^_]*_){0}[^_]*)(*SKIP)(*F)|_", perl=TRUE)
## [[1]]
## [1] "I like" "to see" "how"    "too"   

## > strsplit(x,  "^((?:[^_]*_){1}[^_]*)(*SKIP)(*F)|_", perl=TRUE)
## [[1]]
## [1] "I like_to see" "how_too" 

【讨论】:

  • 第二种解决方案效果很好,第一次出现时无法拆分。 +1
  • 在 R(可能不是所有编译器)中,这将在所有出现的 _ 上分裂。
  • 但是R不是使用PCRE引擎吗? regex101 PCRE 仅在第一次出现时拆分它
  • 我不知道 R 是否足够好,但请尝试:strsplit(gsub("^((?:[^_]*_){0}+[^_]*)_", "\\1~", x, perl=TRUE), "~") as in this demo。这在拆分后给出"I like""to see_how_too"
【解决方案4】:

这使用gsubfn 来预处理输入字符串,以便strsplit 可以处理它。主要优点是可以指定一个数字向量k,指示要拆分的下划线。

它将k定义的下划线替换为双下划线,然后在双下划线处拆分。在本例中,我们在第 2 和第 4 个下划线处分开:

library(gsubfn)

k <- c(2, 4) # split at 2nd and 4th _

p <- proto(fun = function(., x) if (count %in% k) "__" else "_")
strsplit(gsubfn("_", p, "aa_bb_cc_dd_ee_ff"), "__")

给予:

[[1]]
[1] "aa_bb" "cc_dd" "ee_ff"

如果允许空字段,则使用字符串中没有的任何其他字符序列,例如"\01" 代替双下划线。

有关使用 gusbfn 和 proto 对象在匹配之间保留状态的更多信息,请参阅 gusbfn 小插图的第 4 部分。

【讨论】:

    猜你喜欢
    • 2013-03-11
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    • 2019-04-02
    • 2011-10-17
    • 2017-09-08
    • 2021-12-08
    相关资源
    最近更新 更多