如何在Ruby中提取行的一部分?

3

我有一行代码:

line = "start running at Sat April 1 07:30:37 2017"

我想提取出以下部分:

"Sat April 1 07:30:37 2017"

我尝试过这个方法...

line = "start running at Sat April 1 07:30:37 2017"
if (line =~ /start running at/)
   line.split("start running at ").last
end

...但是还有其他的方法吗?


这是具有可读性和高效性的。我不会为了改变而改变它。 - Eric Duminil
6个回答

4

这是一种从任意字符串中提取表示给定格式时间的子字符串的方法。我假设字符串中最多只有一个这样的子字符串。

require 'time'

R = /
    (?:#{Date::ABBR_DAYNAMES.join('|')})\s
              # match day name abbreviation in non-capture group. space
    (?:#{Date::MONTHNAMES[1,12].join('|')})\s
              # match month name in non-capture group, space
    \d{1,2}\s # match one or two digits, space
    \d{2}:    # match two digits, colon
    \d{2}:    # match two digits, colon
    \d{2}\s   # match two digits, space
    \d{4}     # match 4 digits
    (?!\d)    # do not match digit (negative lookahead)
    /x        # free-spacing regex def mode
  # /
  #  (?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s
  #   (?:January|February|March|...|November|December)\s
  # \d{1,2}\s
  # \d{2}:
  # \d{2}:
  # \d{2}\s
  # \d{4}
  # (?!\d)
  # /x 

def extract_time(str)
  s = str[R]
  return nil if s.nil?
  (DateTime.strptime(s, "%a %B %e %H:%M:%S %Y") rescue nil) ? s : nil
end

str = "start eating breakfast at Sat April 1 07:30:37 2017"
extract_time(str)
  #=> "Sat April 1 07:30:37 2017" 

str = "go back to sleep at Cat April 1 07:30:37 2017"
extract_time(str)
  #=> nil

或者,如果与R匹配,但Time#strptime引发异常(意味着给定的时间格式不是s的有效时间),则可以引发一个异常来通知用户。


3

尝试

line.sub(/start running at (.*)/, '\1')

2

使用正则表达式的标准方法是:

if md = line.match(/start running at (.*)/)
  md[1]
end

但是你不需要正则表达式,可以使用常规字符串操作:

prefix = 'start running at '
if line.start_with?(prefix)
  line[prefix.size..-1]
end

1
...或者使用 [String#] 的倒数第二种形式:line[/start running at (.*)/, 1] #=> "Sat April 1 07:30:37 2017" - Cary Swoveland

1

这里还有另一种(事实证明稍微更快)使用#partition的选项:

# will return empty string if there is no match, instead of raising an exception like split.last will
line.partition('start running at ').last

我对这个与正则表达式匹配的性能很感兴趣,因此这里进行了1百万次执行的快速基准测试:

line.sub(/start running at (.*)/, '\1')
# => @real=1.7465

line.partition('start running at ').last
# => @real=0.712406
# => this is faster, but you'd need to be calling this quite a bit for it to make a significant difference

奖励:它还可以轻松适应更一般的情况,例如如果您有以"start running at"开头的行和以"stop running at"开头的其他行。然后像line.partition(' at ').last这样的东西将同时适用于两种情况(并且实际上运行速度略快)。

0
最短的方法是使用line["Sat April 1 07:30:37 2017"],如果存在则返回"Sat April 1 07:30:37 2017"字符串,否则返回nil。在字符串上使用[]符号是从字符串中获取子字符串的一种简写方式,可以与另一个字符串或正则表达式一起使用。请参见https://ruby-doc.org/core-2.2.0/String.html#method-i-5B-5D
如果字符串未知,您也可以像Cary建议的那样使用这个简写方式。
line[/start running at (.*)/, 1]

如果您想确保提取的日期是有效的,您需要使用他的答案中的正则表达式,但仍然可以使用此方法。

3
如果你已经准确地知道了想要的子字符串,那么寻找它的意义何在? - Eric Duminil
1
@Eric,那不太准确。这只告诉你一个字符串是否包含了给定的子字符串,但是你的观点很有道理。 - Cary Swoveland
@EricDuminil 这正是OP所要求的,在这种情况下,它是一种检查字符串是否包含子字符串的方法。 - peter
1
我不这么认为:OP想要检查字符串是否包含“start running at”,如果是这样,提取后面的内容。它可能是“Sat April 1 07:30:37 2017”,也可能是其他内容。在问题“如何将4和3相加?”中,“puts 7”是否是有效答案? - Eric Duminil
从问题本身来看不太清楚,我为这种情况添加了另一个版本。 - peter

0

还有另一种选择:

puts $1 if line =~ /start running at (.*)/

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接