如何编写能够匹配一行或两行文本的正则表达式

时间:2013-02-19 00:40:22

标签: ruby regex

我试图匹配一些可以是一行或两行的文本。我希望能够以有效的方式处理这两种情况。文本字符串将一致地格式化,并包含多个选项卡。我正在尝试用红宝石做比赛。案文如下:

单行:

#3  Hello Stormy    Scratched - Reason Unavailable                           11:10 AM ET 

两行:

#3  Hello Stormy    Scratched - Reason Unavailable                            11:10 AM ET   
                    Scratch Reason - Reason Unavailable changed to Trainer     2:19 PM ET  

我必须在这里使用空格来格式化字符串,但实际的文本使用制表符来分隔各个部分:数字和名称,Scratched以及原因和时间。

示例输出:

一行:#3 Hello Stormy Scratched - 原因在东部时间上午11点10分不可用

两行#3 Hello Stormy Scratched - Reason Unavailable更改为Trainer 2:19 PM

注意:理想情况下,两行输出将包含第一行的数字和名称。

我能够构建一个匹配各个部分的表达式,但是选项卡,第二行以及在两行输出上具有数字和马名称的要求给我带来了麻烦。

3 个答案:

答案 0 :(得分:2)

你不需要花哨的正则表达来做你想做的事情,你只需要知道如何去做。

Ruby的Enumerable有一个名为slice_before的方法,它采用正则表达式,用于确定数组中的哪些元素组合在一起。数组继承自Enumerable。例如:

text = '#3  Hello Stormy    Scratched   -   Reason Unavailable          11:10 AM ET
#3  Hello Stormy    Scratched   -   Reason Unavailable          11:10 AM ET
                        Scratch Reason  -   Reason Unavailable changed to Trainer   2:19 PM ET
'

data = text.split("\n").slice_before(/\A\S/).to_a

require 'pp'
pp data

输出:

[["#3\tHello Stormy\tScratched\t-\tReason Unavailable\t\t\t11:10 AM ET"],
["#3\tHello Stormy\tScratched\t-\tReason Unavailable\t\t\t11:10 AM ET",
  "\t\t\tScratch\tReason\t-\tReason Unavailable changed to Trainer\t2:19 PM ET"]]

换句话说,通过拆分"\n"上的文本创建的数组按不以空格开头的行分组,即模式/\A\S/。所有单行都在单独的子阵列中。作为前一行的延续的行与该行分组。

如果您正在从磁盘读取文件,则可以使用IO.readlines将文件作为数组读取,从而无需拆分文件。

如果需要,您可以进一步处理该数组,使用以下内容重建行和延续行:

data = text.split("\n").slice_before(/\A\S/).map{ |i| i.join("\n") }

data变为:

["#3\tHello Stormy\tScratched\t-\tReason Unavailable\t\t\t11:10 AM ET",
"#3\tHello Stormy\tScratched\t-\tReason Unavailable\t\t\t11:10 AM ET\n\t\t\tScratch\tReason\t-\tReason Unavailable changed to Trainer\t2:19 PM ET"]

如果您需要将每一行拆分为其组成字段,请使用split("\t")。如何在子阵列中执行此操作只是为了练习,但我会涉及map


编辑:

  

...我喜欢你的解决方案,但我正在为slice_before获取未定义的方法。

试试这个:

require 'pp'
require 'rubygems'

class Array

  unless Array.respond_to?(:slice_before)
    def slice_before(pat)
      result = []
      temp_result = []
      self.each do |i|

        if (temp_result.empty?)
          temp_result << i
          next
        end

        if i[pat]
          result << temp_result
          temp_result = []
        end

        temp_result << i
      end
      result << temp_result

    end
  end

end

打电话给:

ary = [
  '#3  Hello Stormy    Scratched - Reason Unavailable                           11:10 AM ET',
  '#3  Hello Stormy    Scratched - Reason Unavailable                            11:10 AM ET',
  '                    Scratch Reason - Reason Unavailable changed to Trainer     2:19 PM ET',
]

pp ary.slice_before(/\A\S/)

看起来像:

[
  ["#3  Hello Stormy    Scratched - Reason Unavailable                           11:10 AM ET"],
  ["#3  Hello Stormy    Scratched - Reason Unavailable                            11:10 AM ET",
   "                    Scratch Reason - Reason Unavailable changed to Trainer     2:19 PM ET"]
]

答案 1 :(得分:1)

如果您可以假设'#'字符没有出现在字符串中的任何其他位置,那么它会相当简化。那么类似的东西应该这样做:

 /^#[^#]*/m

另一种更通用的方法是匹配以#开头的第一行,以及以空格或制表符开头的任何行:

 /^#.*?$(\n^[ \t].*?$)*/m

如果该行并非始终以#开头,则可以将其替换为[^ \t](不是空格或制表符)。

答案 2 :(得分:1)

RE的乐趣!这很糟糕,但那里有几种不同类型的匹配策略。

# Two-line example
s = <<-EOS
  #3\tHello Stormy\t\tScratched - Reason Unavailable\t\t\t11:10 AM ET\t
  \t\t\tScratch Reason - Reason Unavailable changed to Trainer\t2:19 PM ET
EOS
# allow leading/trailing whitespace, get the number, name, last reason and time
s =~ /\A\s*(#\d)\t+([^\t]+)(?:\t+.*)?(?:\t+(.*))\t+(\d+:\d+ (?:AM|PM) ET)\s*\Z/m
# ["#3", "Hello Stormy", "Scratch Reason - Reason Unavailable changed to Trainer", "2:19 PM ET"]
a = $1, $2, $3, $4

注意:这假设您匹配的字符串中只有一条消息 注意:未针对单行案例进行测试:)