Ruby 以编程方式从字符串派生正则表达式

Ruby 以编程方式从字符串派生正则表达式,ruby,regex,string,metaprogramming,grammar,Ruby,Regex,String,Metaprogramming,Grammar,我想输入一个字符串并返回一个正则表达式,可以用来描述字符串的结构。正则表达式将用于查找与第一个相同结构的更多字符串 这是故意含糊其辞的,因为我肯定会错过SO社区中有人会抓住的一个案例 请张贴任何和所有可能的方法来做到这一点 简单的答案(可能不是您想要的答案)是:返回输入字符串(转义正则表达式特殊字符)。这始终是与字符串匹配的正则表达式 如果您希望识别某些结构,则必须提供有关您希望识别的结构类型的更多信息。如果没有这些信息,问题就会以一种含糊不清的方式出现,并且有许多可能的解决方案。例如,输入字符

我想输入一个字符串并返回一个正则表达式,可以用来描述字符串的结构。正则表达式将用于查找与第一个相同结构的更多字符串

这是故意含糊其辞的,因为我肯定会错过SO社区中有人会抓住的一个案例


请张贴任何和所有可能的方法来做到这一点

简单的答案(可能不是您想要的答案)是:返回输入字符串(转义正则表达式特殊字符)。这始终是与字符串匹配的正则表达式

如果您希望识别某些结构,则必须提供有关您希望识别的结构类型的更多信息。如果没有这些信息,问题就会以一种含糊不清的方式出现,并且有许多可能的解决方案。例如,输入字符串“aba”可以描述为

“aba”

“aba*”

“阿坝?”

“ab\w”

“\w{3}”

“(.)b\1”


等等。

很抱歉这么长时间。我把这个前提当作一个小挑战,提出了 用Ruby进行概念验证

我的工作假设是,您可以提供一些字符串,这些字符串应该与正则表达式匹配(HITS),而一些字符串应该与正则表达式不匹配(MISSES)

我的代码基于一个简单的遗传算法实现。请参阅底部的注释,了解我对这种方法的成功与否的看法

LOOP_COUNT = 100

class Attempt

  # let's try email
  HITS    = %w[j@j.com j@j.co.uk gates@microsoft.com sales@microsoft.com sjobs@apple.com sales@apple.com frddy@aol.com thing1@charity.org sales@mybad.org.uk thing.one@drseuss.com]
  MISSES  = %w[j@j j@j@.com j.com @domain.com nochance eric@google. eric@google.com. username-at-domain-dot-com linux.org eff.org microsoft.com sjobs.apple.com www.apple.com]

  # odd mixture of numbers and letters, designed to confuse
  # HITS = %w[a123 a999 a600 a545 a100 b001 b847 a928 c203]
  # MISSES = %w[abc def ghi jkl mno pqr stu vwx xyz h234 k987]

  # consonants versus vowels
  # HITS = %w[bcd cdb fgh ghf jkl klj mnp npm qrs srq tvw vwt xzb bzx]
  # MISSES = %w[aei oyu oio euu uio ioe aee ooo]

  # letters < 11 chars and no numbers
  # HITS = %w[aaa aaaa abaa azaz monkey longstring stringlong]
  # MISSES = %w[aa aa1 aa0 b9b 6zz longstringz m_m ff5 666 anotherlongstring]

  MAX_SUCCESSES = HITS.size + MISSES.size

  # Setup the various Regular Expression operators, etc..
  RELEMENTS = %w[. ? * + ( ) \[ \] - | ^ $ \\ : @ / { }]
  %w[A b B d D S s W w z Z].each do |chr|
    RELEMENTS << "\\#{chr}"
  end
  %w[alnum alpha blank cntrl digit lower print punct space upper xdigit].each do |special|
    RELEMENTS << "[:#{special}:]"
  end
  ('a'..'z').each do |chr|
    RELEMENTS << chr
  end
  ('A'..'Z').each do |chr|
    RELEMENTS << chr
  end
  (0..9).each do |chr|
    RELEMENTS << chr.to_s
  end

  START_SIZE = 8

  attr_accessor :operators, :successes

  def initialize(ary = [])
    @operators = ary
    if ary.length < 1
      START_SIZE.times do
        @operators << random_op
      end
    end
    @score = 0
    @decay = 1
    make_regexp
  end

  def make_regexp
    begin
      @regexp = Regexp.new( @operators.join("") )
    rescue
      # "INVALID Regexp"
      @regexp = nil
      @score = -1000
    end
  end

  def random_op
    RELEMENTS[rand(RELEMENTS.size)]
  end

  def decay
    @decay -= 1
  end

  def test
    @successes = 0
    if @regexp
      HITS.each do |hit|
        result = (hit =~ @regexp)
        if result != nil
          reward
        end
      end
      MISSES.each do |miss|
        result = (miss =~ @regexp)
        if result == nil
          reward
        end
      end
    end
    @score = @successes
    self
  end

  def reward
    @successes += 1
  end

  def cross other
    len = size
    olen = other.size
    split = rand(len)
    ops = []
    @operators.length.times do |num|
      if num < split
        ops << @operators[num]
      else
        ops << other.operators[num + (olen - len)]
      end
    end
    Attempt.new ops
  end

  # apply a random mutation, you don't have to use all of them
  def mutate
    send [:flip, :add_rand, :add_first, :add_last, :sub_rand, :sub_first, :sub_last, :swap][rand(8)]
    make_regexp
    self
  end

  ## mutate methods
  def flip
    @operators[rand(size)] = random_op
  end
  def add_rand
    @operators.insert rand(size), random_op
  end
  def add_first
    @operators.insert 0, random_op
  end
  def add_last
    @operators << random_op
  end
  def sub_rand
    @operators.delete_at rand(size)
  end
  def sub_first
    @operators.delete_at 0
  end
  def sub_last
    @operators.delete_at size
  end
  def swap
    to = rand(size)
    begin
      from = rand(size)
    end while to == from
    @operators[to], @operators[from] = @operators[from], @operators[to]
  end

  def regexp_to_s
    @operators.join("")
  end

  def <=> other
    score <=> other.score
  end

  def size
    @operators.length
  end

  def to_s
    "#{regexp_to_s} #{score}"
  end

  def dup
    Attempt.new @operators.dup
  end

  def score
    if @score > 0
      ret = case
      when (size > START_SIZE * 2)
        @score-20
      when size > START_SIZE
        @score-2
      else
        @score #+ START_SIZE - size
      end
      ret + @decay
    else
      @score + @decay
    end
  end

  def == other
    to_s == other.to_s
  end

  def stats
    puts "Regexp #{@regexp.inspect}"
    puts "Length #{@operators.length}"
    puts "Successes #{@successes}/#{MAX_SUCCESSES}"
    puts "HITS"
    HITS.each do |hit|
      result = (hit =~ @regexp)
      if result == nil
        puts "\tFAIL #{hit}"
      else
        puts "\tOK #{hit} #{result}"
      end
    end
    puts "MISSES"
    MISSES.each do |miss|
      result = (miss =~ @regexp)
      if result == nil
          puts "\tOK #{miss}"
        else
          puts "\tFAIL #{miss} #{result}"
      end
    end
  end

end

$stderr.reopen("/dev/null", "w") # turn off stderr to stop streams of bad rexexp messages

# find some seed attempt values
results = []
10000.times do
  a = Attempt.new
  a.test
  if a.score > 0
    # puts "#{a.regexp_to_s} #{a.score}"
    results << a
  end
end

results.sort!.reverse!

puts "SEED ATTEMPTS"
puts results[0..9]

old_result = nil

LOOP_COUNT.times do |i|
  results = results[0..9]
  results.map {|r| r.decay }
  3.times do
    new_results = results.map {|r| r.dup.mutate.test}
    results.concat new_results
    new_results = results.map {|r| r.cross( results[rand(10)] ).test }
    results.concat new_results
  end
  new_results = []
  20.times do
    new_results << Attempt.new.test
  end
  results.concat new_results
  results.sort!.reverse!
  if old_result != results[0].score
    old_result = results[0].score
  end
  puts "#{i}   #{results[0]}"
end
puts "\n--------------------------------------------------"
puts "Winner! #{results[0]}"
puts "--------------------------------------------------\n"
results[0].stats
LOOP\u COUNT=100
班级尝试
#让我们试试电子邮件
点击数=%w[j@j.com j@j.co.uk gates@microsoft.com sales@microsoft.com sjobs@apple.com sales@apple.com frddy@aol.com thing1@charity.org sales@mybad.org.uk事情one@drseuss.com]
未命中=%w[j@j j@j@.com j.com@domain.com nochanceeric@google. eric@google.com.域名:dot.com linux.org eff.org microsoft.com sjobs.apple.com www.apple.com的用户名]
#数字和字母的奇数混合,旨在混淆
#点击次数=%w[a123 a999 a600 a545 a100 b001 b847 a928 c203]
#未命中=%w[abc def ghi jkl mno pqr stu vwx xyz h234 k987]
#辅音与元音
#点击数=%w[bcd cdb fgh ghf jkl klj mnp npm qrs srq tvw vwt xzb bzx]
#未命中=%w[aei oyu oio euu uio ioe aee ooo]
#字母<11个字符且无数字
#点击数=%w[aaa aaaa abaa azaz猴子长串串长串]
#未命中=%w[aa aa1 aa0 b9b 6zz长串m_m ff5 666其他长串]
最大成功率=命中率.size+未命中率.size
#设置各种正则表达式运算符等。。
相关性=%w[.?*+()\[\]-\^$\\:@/{}]
%w[A b b d S w z z]。每个do | chr|

relationsimport刚刚发布了一个从示例字符串集派生正则表达式模式的免费工具:“给它一些你想提取的数据示例,它将以编程方式生成和测试正则表达式。”

它是免费的,但确实需要登录才能使用它

免责声明:我在import.io工作(这就是我知道它存在的原因)

这实际上允许您从示例文本生成正则表达式。您可以选择要使用正则表达式的文本字符串的一部分,它会以您选择的语言为您生成正则表达式


看看它,它的FAQ中解释了一个例子-

人类甚至不能很好地做到这一点(看看stackoverflow上一些可怕的正则表达式问题),我们应该擅长检测模式。我怀疑这是可以做到的,除非你有一个非常复杂的人工智能和一个大的训练数据集。但是,仅仅一个输入字符串几乎不能产生任何有用的东西。@我们的想法是在训练AI时使用这些模式。更糟糕的是,输入
'aba'
可以最简单地描述为
/./
-就像任何其他字符串一样。@Chuck-除了
“\n”
。你可能是指
/.*/s
。非常聪明。我想要一个工具,它可以生成一个正则表达式来描述输入中的差异,但除了Confusion的第一个答案“returnthestring”之外,再也没有比这更进一步的了。和你一样,我不确定这个工具是否真的值得,但读起来还是很有趣的