【问题标题】:How to extract portion of a line in ruby?如何在红宝石中提取一行的一部分?
【发布时间】:2017-04-01 14:35:20
【问题描述】:

我有句话说

line = "2017 年 4 月 1 日星期六 07:30:37 开始运行"

我想提取

“2017 年 4 月 1 日星期六 07:30:37”

我试过了……

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

...但是有没有其他方法可以做到这一点?

【问题讨论】:

  • 可读且高效。我不会为了改变它而改变它。

标签: ruby regex split


【解决方案1】:

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

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 不是给定时间格式的有效时间),则可以引发异常以通知用户。

【讨论】:

    【解决方案2】:

    试试

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

    【讨论】:

      【解决方案3】:

      使用正则表达式执行此操作的标准方法是:

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

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

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

      【讨论】:

      • ...或使用String#[]的倒数第二个形式:line[/start running at (.*)/, 1] #=> "Sat April 1 07:30:37 2017"
      【解决方案4】:

      这是另一个使用#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
      

      我很感兴趣这对正则表达式匹配的表现如何,所以这里有一个快速基准,每个执行 100 万次:

      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 这样的东西会同时满足两者(并且实际上运行得更快)。

      【讨论】:

        【解决方案5】:

        还有另一种选择:

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

        【讨论】:

          【解决方案6】:

          最短的是line["Sat April 1 07:30:37 2017"],如果存在则返回“Sat April 1 07:30:37 2017”字符串,如果不存在则返回 nil。 String 上的 [] 表示法是从字符串中获取子字符串的简写,可以与另一个字符串或正则表达式一起使用。见https://ruby-doc.org/core-2.2.0/String.html#method-i-5B-5D

          如果字符串未知,您也可以像 Cary 建议的那样使用此速记

          line[/start running at (.*)/, 1]
          

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

          【讨论】:

          • 如果您确切知道要作为输出的子字符串,那么寻找它的意义何在?
          • @Eric,这不太准确。这会告诉您字符串是否包含给定的子字符串,但您的观点是正确的。
          • @EricDuminil 这正是 OP 所要求的,在这种情况下,它是一种检查字符串是否包含子字符串的方法
          • 我不这么认为:OP想要检查字符串是否包含"start running at ",如果是这样,提取后面的内容。可能是"Sat April 1 07:30:37 2017",也可能是别的东西。 puts 7 是“如何添加 4 和 3?”问题的有效答案。
          • 问题本身并不清楚,我为这种情况添加了另一个版本..
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多