最简单的方法是将字符串大致匹配到列表并在Ruby中返回值?

时间:2012-10-20 16:22:47

标签: ruby pattern-matching yaml

我想在Ruby中构建一个函数,它接受一个字符串,并在返回一些值或函数之前将它与一系列可能性进行比较。这是一种(可能是天真的)方式,它可以写成一个案例陈述:

a = 'unicorn'
case a
  when "gnome", "dwarf", "hobbit"
    "dwarf"
  when "dragon"
    puts "run away!"
  when "centaur", "unicorn"
    magical_equine_function(a)
  else
    false
end

我的问题是,如何使用像amatch这样的近似匹配库来做同样的事情,这样你就可以设置一个匹配值(比如2),当你有一个=时仍然会得到匹配Unicorn“或a =”uncorn“?

此外,是否有一种更清晰,更易于维护的方法来通过YAML文件播种/比较所有可能的匹配来编写所有这些,而不是写出大量的case语句?我是YAML的新手,但希望我能够只加载一个文件:

? 
  -"gnome"
  -"dwarf"
  -"hobbit"
: 
  -"dwarf"
? 
  -"dragon"
: 
  -puts "run away"
?
  -"centaur"
  -"unicorn"
: 
  -magical_equine_function(a)

然后只对加载的文件进行近似匹配。有没有办法做到这一点,不会杀死性能?

1 个答案:

答案 0 :(得分:1)

有几种方法可以给这只猫上皮。一个是lambdas:

MATCHERS = [
  [/^gnome|dwarf|hobbit$/, lambda { |a| 'dwarf' }],
  ['dragon', lambda { |a| puts 'run away!' }],
  [/^centaur|unicorn$/, lambda { |a| magical_equine_function(a) }],
  [//, lambda { |a| false }],
]

它将模式(可以是字符串或正则表达式)与lambdas相关联。最后一个匹配器是特殊的:它是一个匹配任何东西的哨兵。它代表示例中的 else 子句。

以下是进行匹配的代码:

def match(a)
  MATCHERS.each do |pattern, f|
    return f[a] if pattern === a
  end
end

我们使用===,以便可以使用字符串或正则表达式。

以下是它的使用方法:

p match('gnome')
# => "dwarf"

p match('dragon')
# => "run away!"
# => nil

p match('unicorn')
# => "equine unicorn"

p match('oddball')
# => false

您也可以使用方法:

class Matcher

  def match(a)
    MATCHERS.each do |pattern, method|
      return send(method, a) if pattern === a
    end
  end

  private

  MATCHERS = [
    [/^gnome|dwarf|hobbit$/, :dwarf],
    ['dragon', :dragon],
    [/^centaur|unicorn$/, :equine],
    [//, :default],
  ]

  def dwarf(a)
    "dwarf"
  end

  def dragon(a)
    puts "run away!"
  end

  def equine(a)
    magical_equine_function(a)
  end

  def default(a)
    false
  end

  def magical_equine_function(a)
    "equine #{a}"
  end

end

并在使用中:

matcher = Matcher.new
    p matcher.match('gnome')
# => "dwarf"

# etc.

使用方法将代码与匹配规则分开,如果需要,可以将匹配规则放在文件中。这是match_rules

的内容
---
- - !ruby/regexp /^gnome|dwarf|hobbit$/
  - :dwarf
- - dragon
  - :dragon
- - !ruby/regexp /^centaur|unicorn$/
  - :equine
- - !ruby/regexp //
  - :default

我们不需要对Matcher类进行很多更改就可以使其工作:

require 'yaml'

class Matcher

  def initialize
    @matchers = YAML.load_file('match_rules')
  end

  def match(a)
    @matchers.each do |pattern, method|
      return send(method, a) if pattern === a
    end
  end

  # ...

end

对来自文件的数据使用send允许创建该文件的任何人使您调用任何Matcher类的方法。为防止这种情况发生,您只需为规则中使用的所有方法添加前缀:

def matched_dragon(a)
  puts "run away!"
end

# etc.

在匹配方法中,添加该前缀:

def match(a)
    @matchers.each do |pattern, method|
      return send("matched_#{method}", a) if pattern === a
    end
  end

现在只有匹配函数可以调用前缀为“matched_”的方法。


在评论中回答后续问题:

模式的YAML是一个字符串数组,如下所示:

---
- - - gnome
    - dwarf
    - hobbit
  - :small_person
- - - dragon
  - :dragon