在不删除任何字符的情况下,在正则表达式匹配时拆分字符串

时间:2015-09-26 20:32:29

标签: ruby regex

我想在日期上拆分此文本,但不从字符串中删除日期:

sep 25 fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames
   at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @
sep 25 fri The Holdup, The Wheeland Brothers
   at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **

数组中的第一个元素是:

sep 25 fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames
   at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @`

条目具有可变行数,因此我无法拆分新行。

日期格式为:

month_abbreviation + space(or two) + day_number

像这样的伪代码:

three_letter_word + whitespace(s) + one_or_two_digit_number

会奏效。

6 个答案:

答案 0 :(得分:2)

Ruby有一个很棒的方法,它是Array(继承自Enumerable)的一部分,名为slice_before。我会像以下一样使用它:

str = <<EOT
sep 25 fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames
    at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @
sep 25 fri The Holdup, The Wheeland Brothers
    at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **
EOT

MONTHS = %w[jan feb mar apr may jun jul aug sep oct nov dec]
MONTH_PATTERN = Regexp.union(MONTHS).source # => "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec"
MONTH_REGEX = /^(?:#{ MONTH_PATTERN })\b/i # => /^(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\b/i

schedule = str.lines.slice_before(MONTH_REGEX).to_a
# => [["sep 25 fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames\n",
#      "    at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @\n"],
#     ["sep 25 fri The Holdup, The Wheeland Brothers\n",
#      "    at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **\n"]]

schedule[0]
# => ["sep 25 fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames\n",
#     "    at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @\n"]

schedule[1]
# => ["sep 25 fri The Holdup, The Wheeland Brothers\n",
#     "    at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **\n"]

slice_before对字符串不起作用,它适用于Array或Enumerator,因此第一步是使用lines基于行尾分割字符串,这将返回一个枚举器。 slice_before然后查看数组中的每个元素,并根据匹配MONTH_REGEX找到的匹配创建子数组。

/^(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\b/i基本上是说“从字符串的开头开始,找到与三个字母的月份名称匹配的单词,无论它们的字母是什么”。

因为它是用于匹配“前切片”点的正则表达式,所以很容易定制需要匹配的确切模式。在这种特殊情况下,具有前导空白行的行是连续行,换句话说,它们是次要的,而不是最重要的。偶尔会看到这种数据输出。没有前导空格的行是断行,表示新记录的开头。我可以使用/^\S/的模式打破,这意味着“找到一条以不是空白的东西开头的行,但我觉得匹配更具体的东西,月缩写,非常有用且具体而不浪费时间在匹配过程中。/^\w{3} \d{1,2} \w{3} /也可以工作,但由于^因为匹配的子字符串必须出现在字符串的开头,所以它会过度,但是如果这没有意义那么请阅读Regexp在IRB中课程的文档和实验,因为它一点都不难理解。

join如果你愿意,可以将子数组重新转换为字符串:

schedule.map(&:join)
# => ["sep 25 fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames\n    at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @\n",
#     "sep 25 fri The Holdup, The Wheeland Brothers\n    at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **\n"]

这是我们内部使用的一种技术,用于拆分巨型配置文件,将它们分成几行并找到带有正则表达式的部分的标记。

答案 1 :(得分:1)

您指定要分割日期。因此,我没有拆分任何具有指定日期格式且无法转换为日期的字符串,包括"Sep 31 Sat""Sep 26 Wed"(后者,今年为"Sat")。我假设日期子字符串可以出现在字符串中的任何位置。如果你想要求他们从每一行的开头开始,那当然是一个简单的修改。

str =
"sep 25 fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames
       at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @
sep 31 mon at some other place 
oct 26 sat The Holdup, The Wheeland Brothers
       at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **"

require 'date'

arr = str.split.
          map(&:capitalize).
          each_cons(3).
          map { |a| a.join(' ') }.
          select { |s| Date.strptime(s, '%b %d %a') rescue nil }
  #=> ["Sep 25 Fri", "Oct 26 Sat"]

r = /(#{ arr.join('|') })/i
  #=> /(Sep 25 Fri|Oct 26 Sat)/i

str.split(r)
  #=" ["",
  #    "sep 25 fri",
  # " The Phenomenauts, The Atom Age, Los Pistoleros, The Shames\n\
  #  at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @\n    sep 31\
  #   mon at some other place \n    ",
  # "oct 26 sat",
  # " The Holdup, The Wheeland Brothers\n           at the El Rey Theatre,\
  #   Chico 18+ (a/a with adult) 7:30pm/8:30pm **"]

要避免返回数组开头和结尾的空字符串,请使用:

str.split(r).delete_if(&:empty?)

答案 2 :(得分:0)

假设OP的描述:

  

three_letter_word + whitespace(s)+ one_or_two_digit_number将起作用

是对的,

text.split(/(?=\w{3} +\d{1,2})/)

答案 3 :(得分:0)

有12个月零7天,所以你可以选择它们:

text = <<txt
sep 25 fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames
       at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @
sep 25 The Holdup, The Wheeland Brothers
       at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **
txt

text.split(/((?:jan|feb|mar|apr|may|jun|ju|aug|sep|oct|nov|dec)\s+[12]?\d)/).each{|part|
  p part
}
p '-------------'
text.split(/((?:jan|feb|mar|apr|may|jun|ju|aug|sep|oct|nov|dec)\s+[12]?\d(?:\s*(?:mon|tue|wed|thu|fri|sat|sun))?)/).each{|part|
  p part
}

结果:

""
"sep 25"
" fri The Phenomenauts, The Atom Age, Los Pistoleros, The Shames\n       at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @\n"
"sep 25"
" The Holdup, The Wheeland Brothers\n       at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **\n"
"-------------"
""
"sep 25 fri"
" The Phenomenauts, The Atom Age, Los Pistoleros, The Shames\n       at Jub Jubs, 71 S Wells Avenue, Reno, NV 21+ 8pm *** @\n"
"sep 25"
" The Holdup, The Wheeland Brothers\n       at the El Rey Theatre, Chico 18+ (a/a with adult) 7:30pm/8:30pm **\n"

正则表达式的一些细节:

  • (?:....)避免匹配的部分成为结果的一部分($ 1,$ 2 ......)
  • 只有完整的日期匹配没有(?:,并且会成为结果的一部分。
  • 如果没有最外层的(),则会在结果中删除匹配项。
  • 我的示例中的正则表达式区分大小写。
  • [123]?\d检查可选的1,2或3和另一个号码。这将允许日期数字,如32,33 ......

答案 4 :(得分:0)

由于您需要拆分每个日期,因此您需要确定匹配过程中正则表达式引擎的位置。您可以使用前瞻?=,然后捕获您想要的令牌,以实现此目的。

例如,这种模式(?=[a-zA-Z]{3}\s+\d{1,2}\s+[a-zA-Z]{6,9})

这里,正则表达式引擎将位于任何单词的起始位置,其中包含三个字母,后跟一个或多个空格,一个或两个数字,一个或多个空格,以及一个6到9的单词信件,例如。 sep 25 Friday。在此示例中,正则表达式引擎位于s中的sep之前。使用这些知识,您现在可以使用您选择的任何编程语言拆分String。

line.split(/?=[a-zA-Z]{3}\s+\d{1,2}\s+[a-zA-Z]{6,9}/);

?=:这是一个前瞻性,与要捕获的正则表达式令牌之前的位置相匹配。

[a-zA-Z]{3}:匹配3个字,因为月份是单词而不是数字,例如sep

\s+\d{1,2}:匹配一个或多个空格,后跟一个或两个数字

\s+[a-zA-Z]{6,9}:匹配一个或多个空格,后跟至少6个单词,最多9个单词,因为一周中数字最少的一天是Friday(6个字母)和最高为Wednesday(9个字母)

答案 5 :(得分:-2)

我可以看到除了每条记录的第一行之外的行都缩进了几个空格,因此您可以使用str.split(/\n(?!\s+)/)进行拆分。