在Ruby中从String中提取参数的最有效方法

时间:2012-07-16 23:55:49

标签: ruby string parsing

我想通过只读取一次字符串(O(n)时间复杂度)从Ruby中的字符串中提取一些信息。

以下是一个例子:

字符串如下所示:-location here -time 7:30pm -activity biking

我想要填充此信息的Ruby对象。所有关键字都是已知的,它们都是可选的。

def ActivityInfo
  _attr_reader_ :location, :time, :activity

  def initialize(str)
    @location, @time, @activity = DEFAULT_LOCATION, DEFAULT_TIME, DEFAULT_ACTIVITY

    # Here is how I was planning on implementing this
    current_string = ""
    next_parameter = nil # A reference to keep track of which parameter the current string is refering to
    words = str.split
    while !str.empty?
      word = str.shift
      case word
      when "-location"
        if !next_parameter.nil?
          next_parameter.parameter = current_string # Set the parameter value to the current_string
          current_string = ""
        else
        next_parameter = @location
      when "-time"
        if !next_parameter.nil?
          next_parameter.parameter = current_string
          current_string = ""
        else
        next_parameter = @time
      when "-activity"
        if !next_parameter.nil?
          next_parameter.parameter = current_string
          current_string = ""
        else
        next_parameter = @time
      else
        if !current_string.empty?
          current_string += " "
        end
        current_string += word
      end
    end
  end   
end

所以基本上我只是不知道如何使变量成为另一个变量或方法的引用,以便我可以将其设置为特定值。或者也许只有另一种更有效的方法来实现这一目标?

谢谢!

3 个答案:

答案 0 :(得分:2)

该字符串看起来很像命令行,并且有一些很好的Ruby模块可以解析它们,例如optparse

假设不是,这里有一种快速的方法将样本中的命令解析为哈希:

cmd = '-location here -time 7:30pm -activity biking'
Hash[*cmd.scan(/-(\w+) (\S+)/).flatten]

结果是:

{
    "location" => "here",
        "time" => "7:30pm",
    "activity" => "biking"
}

将它扩展得更远:

class ActivityInfo
  def initialize(h)
    @location = h['location']
    @time     = h['time'    ]
    @activity = h['activity']
  end
end
act = ActivityInfo.new(Hash[*cmd.scan(/-(\w+) (\S+)/).flatten])

act设置为ActivityInfo的实例,如下所示:

#<ActivityInfo:0x101142df8
    @activity = "biking",
    @location = "here",
    @time = "7:30pm"
>

-

OP询问如何处理命令未标记-或多个单词的情况。这些是等价的,但我更喜欢风格上的第一个:

irb(main):003:0> cmd.scan(/-((?:location|time|activity)) \s+ (\S+)/x)
[
    [0] [
        [0] "location",
        [1] "here"
    ],
    [1] [
        [0] "time",
        [1] "7:30pm"
    ],
    [2] [
        [0] "activity",
        [1] "biking"
    ]
]

irb(main):004:0> cmd.scan(/-(location|time|activity) \s+ (\S+)/x)
[
    [0] [
        [0] "location",
        [1] "here"
    ],
    [1] [
        [0] "time",
        [1] "7:30pm"
    ],
    [2] [
        [0] "activity",
        [1] "biking"
    ]
]

如果命令是多个单词,例如“at location”:

irb(main):009:0> cmd = '-at location here -time 7:30pm -activity biking'
"-at location here -time 7:30pm -activity biking"
irb(main):010:0> 
irb(main):011:0* cmd.scan(/-((?:at \s location|time|activity)) \s+ (\S+)/x)
[
    [0] [
        [0] "at location",
        [1] "here"
    ],
    [1] [
        [0] "time",
        [1] "7:30pm"
    ],
    [2] [
        [0] "activity",
        [1] "biking"
    ]
]

如果您需要更多灵活性,请查看Ruby的strscan模块。您可以使用它来拆分字符串并找到命令及其参数。

答案 1 :(得分:1)

将字符串转换为选项哈希

如果您只想轻松访问标志及其值,可以将字符串拆分为散列,其中每个标志都是一个键。例如:

options = Hash[ str.scan /-(\w+)\s+(\S+)/ ]
=> {"location"=>"here", "time"=>"7:30pm", "activity"=>"biking"}

然后,您可以直接引用值(例如options['location'])或在键/值对中迭代哈希值。例如:

options.each_pair { |k, v| puts "%s %s" % [k, v] }

元编程的短跑

好吧,这是严重的过度工程,但我在这个问题上花了一点时间,因为我发现它很有趣。我没有声称以下内容很有用;我只是说我这很有趣。

如果你想解析你的选项标志并动态创建一组属性读者设置一些实例变量而不必分别定义每个标志或变量,你可以做一下元编程。

# Set attribute readers and instance variables dynamically
# using Kernel#instance_eval.
class ActivityInfo
  def initialize(str)
    options = Hash[ str.scan /-(\w+)\s+(\S+)/ ]
    options.each_pair do |k, v|
      self.class.instance_eval { attr_reader k.to_sym }
      instance_variable_set("@#{k}", v)
    end
  end
end

ActivityInfo.new '-location here -time 7:30pm -activity biking'
=> #<ActivityInfo:0x00000001b49398
 @activity="biking",
 @location="here",
 @time="7:30pm">

老实说,我认为从选项哈希中明确设置变量,例如:

@activity = options['activity']`

会更清楚地传达你的意图(并且更具可读性),但是有其他选择总是好的。您的里程可能会有所不同。

答案 2 :(得分:0)

为什么Thor可以为你做重物时重新发明轮子?

class ActivityInfo < Thor

  desc "record", "record details of your activity"
  method_option :location, :type => :string,   :aliases => "-l", :required => true
  method_option :time,     :type => :datetime, :aliases => "-t", :required => true
  method_option :activity, :type => :string,   :aliases => "-a", :required => true
  def record
    location = options[:location]
    time = options[:time]
    activity = options[:activity]

    # record details of the activity
  end

end

将根据您指定的数据类型为您解析选项。您可以通过编程方式调用它:

task = ActivityInfo.new([], {location: 'NYC', time: Time.now, activity: 'Chilling out'})
task.record

或者从命令行:thor activity_info:record -l NYC -t "2012-06-23 02:30:00" -a "Chilling out"