【问题标题】:Regex: text before multiple matches正则表达式:多个匹配之前的文本
【发布时间】:2015-09-12 04:01:03
【问题描述】:

想法。给定字符串,返回所有匹配项(有重叠)和这些匹配项之前的文本。

示例。对于文本atatgcgcatatat 和查询atat,有三个匹配项,所需的输出是atatatatgcgcatatatatgcgcatatat

问题。我使用 Ruby 2.2 和 String#scan 方法来获得多个匹配项。我尝试使用前瞻,但正则表达式/(?=(.*?atat))/ 返回以atat 结尾的每个子字符串。必须有一些正则表达式的魔法来解决这个问题,但我想不出正确的咒语。

【问题讨论】:

  • 我猜是不可能一口气完成的。匹配不只是重叠,它们从相同的索引开始。您需要在成功后将索引移动到每个匹配开始,以查看是否有其他匹配不等于前一个。
  • 非常感谢您的 cmets!
  • 反转字符串,并使用相同的模式。
  • @CasimiretHippolyte 我已经尝试过反转字符串的想法,但对我来说似乎相当难看:"atatgcatatat".reverse.scan(/(?=(tata.*))/).flatten.map(&:reverse).reverse
  • 我对最佳答案的投票投给了sawa。 @Nakilon 获得协助(无论是否需要)。

标签: ruby regex


【解决方案1】:

我相信这至少比 OP 的回答要好:

text = "atatgcgcatatat"
query = "atat"

res = []
text.scan(/(?=#{query})/){res.push($` + query)}                                  #`
res # => ["atat", "atatgcgcatat", "atatgcgcatatat"]

【讨论】:

  • 绝对是!我也在 Ruby 中实现了其他想法,并将它们添加到我的答案中。
  • 我猜,不需要中间数组:text.to_enum(:scan, /(?<=atat)/).map { $` }
  • 你可以像我一样使用tap[].tap { |a| text.scan(/(?=#{query})/) {a << $` + query} } #=> ["atat", "atatgcgcatat", "atatgcgcatatat"]
【解决方案2】:

鉴于正则表达式的性质和目的,没有办法做到这一点。当正则表达式匹配文本时,无法在另一个匹配中包含相同的文本。因此,我能想到的最佳选择是使用后视来查找每场比赛的结束位置:

(?<=atat)

使用您的示例输入 atatgcgcatatat,这将返回以下三个匹配项:

  • 位置 4,长度 0
  • 位置 12,长度 0
  • 位置 14,长度 0

然后您可以遍历这些结果,获取每个结果的位置,然后获取从输入字符串的开头开始并在该位置结束的子字符串。如果您不知道如何获取每场比赛的位置,您可能会发现this question 的答案很有帮助。

【讨论】:

  • 但是如何获得多个匹配项? "atatgcgcatatat".scan /.*atat/ #=&gt; ["atatgcgcatatat"]
  • 谢谢,这很有帮助!由于无法一次性获得输出,因此我还找到了一种获得结果的方法,它与您提出的略有不同。我将其作为单独的答案发布。
  • 谢谢@Nakilon,这很有用!
【解决方案3】:

你可以这样做:

str = 'atatgcgcatatat'
target = 'atat'

[].tap do |a|
  str.gsub(/(?=#{target})/) { a << str[0, $~.end(0)+target.size] }
end
  #=> ["atat", "atatgcgcatat", "atatgcgcatatat"]

注意gsub返回的字符串被丢弃了。

【讨论】:

    【解决方案4】:

    看来,没有办法一口气解决问题。

    一种可能的解决方案是在使用String#scan 时使用this knowledge 获取匹配索引,然后返回切片字符串数组:

    def find_by_end text, query
        res = []
        n = query.length
        text.scan( /(?=(#{query}))/ ) do |m|
            res << text.slice(0, $~.offset(0).first + n)
        end
        res
    end
    
    find_by_end "atatgcgcatatat", "atat" #=> ["atat", "atatgcgcatat", "atatgcgcatatat"]
    

    @StevenDoggart 提出了一个稍微不同的解决方案。这是一个漂亮而简短的代码,它使用this hack 来解决问题:

    "atatgcatatat".to_enum(:scan, /(?<=atat)/).map { $` }                         #`
    #=> ["atat", "atatgcatat", "atatgcatatat"]
    

    正如@CasimiretHippolyte 所说,反转字符串可能有助于解决问题。确实如此,但这并不是最漂亮的解决方案:

    "atatgcatatat".reverse.scan(/(?=(tata.*))/).flatten.map(&:reverse).reverse
    #=> ["atat", "atatgcatat", "atatgcatatat"]
    

    【讨论】:

      猜你喜欢
      • 2017-12-16
      • 1970-01-01
      • 1970-01-01
      • 2019-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多