2010-06-28 62 views
5

我想輸入一個字符串並返回可用於描述字符串結構的正則表達式。正則表達式將用於查找更多與第一個相同結構的字符串。以編程方式從字符串派生正則表達式

這是故意模棱兩可的,因爲我肯定會錯過SO社區中的某個人會抓到的案例。

請發佈任何和所有可能的方式來做到這一點。

+4

人類甚至不能做到這一點那麼好(只是看一些計算器上的可怕的正則表達式給出的問題),我們」我們應該善於檢測模式。我懷疑這可以做,除非你有一個相當複雜的人工智能與大量的訓練數據集。不過,只有一個輸入字符串幾乎沒有任何用處。 – polygenelubricants 2010-06-28 19:42:18

+0

@polygenelubricants這個想法是使用這些模式來訓練AI。 – smothers 2010-06-28 19:44:34

回答

12

瑣碎的答案,可能不是你想要的是:返回輸入字符串(與正則表達式特殊字符轉義)。這總是一個匹配字符串的正則表達式。

如果您希望識別某些結構,您必須提供有關您希望識別的結構類型的更多信息。如果沒有這些信息,問題就會以模糊的方式陳述出來,並且有許多可能的解決方案。例如,輸入串 'ABA' 可被描述爲

'ABA'

'ABA *'

「ABA?

'AB \ W'

'\瓦特{3}'

'()。B \ 1'

+8

更糟糕的是,輸入「aba」可以簡單地描述爲「/.*/」,正如任何其他字符串一樣。 – Chuck 2010-06-28 20:00:18

+0

@Chuck - 除了'「\ n」'。你可能意思是'/.*/ s'。 – Adrian 2010-06-28 21:38:35

+0

@Adrian:或者只是'//' – 2010-06-28 22:27:57

5

抱歉的此的長度。我將這個前提作爲一個小小的挑戰,並且在Ruby中提出了一個概念證明 。

我工作的假設是你可以提供一些應該匹配正則表達式(HITS)的字符串和一個不匹配的數字(MISSES)。

我基於遺傳算法的天真執行的代碼。請參閱底部的註釋以瞭解我對這種方法的成功或其他方面的看法。

LOOP_COUNT = 100 

class Attempt 

    # let's try email 
    HITS = %w[[email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected]] 
    MISSES = %w[[email protected] [email protected]@.com j.com @domain.com nochance [email protected] [email protected] 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 

使用此代碼學到的經驗教訓。總體而言,看起來多次運行較短的循環最有可能產生可用的結果。但是,這可能是由於我的實現,而不是遺傳算法的性質。

您可能會得到可行的結果,但仍含有亂碼的部分。

您將需要非常牢固掌握正則表達式,以瞭解有多少結果真正實現了他們所做的。

最終你的學習時間可能比嘗試使用此代碼作爲快捷方式學習正則表達式要好得多。我意識到提問者可能沒有這個動機,我嘗試這個動機的原因是因爲這是一個有趣的想法。

結果有很多折衷。你提供的HITS和MISSES越多樣化,產生結果的時間越長,你需要運行的環路也越多。每一個都少一個可能會產生一個結果,這個結果要麼是針對您提供的字符串的大量特定結果,要麼是通用的,以至於在現實世界中無法使用。

我也硬編碼了一些假設,比如標記時間過長的表達式。

+0

非常聰明。我想要一個能夠生成正則表達式的工具來描述輸入的差異,但從來沒有比Confusion的第一個答案「返回字符串」更進一步。像你一樣,我不確定這個工具是否真的值得,但讀起來很有趣。 :) – sarnold 2011-10-04 22:11:27

0

進口剛剛發佈了一個免費的工具,獲得來自集例如字符串的正則表達式模式:「給它的數據的例子,你要拉出來,它會編程方式生成和測試正則表達式。」

https://regexpgen.import.io/

其免費的,但確實需要登錄來使用它。

enter image description here

聲明:我在import.io工作(這就是我如何知道存在)

0

site其實,您可以生成從示範文本正則表達式。你選擇你想要的正則表達式的文本字符串的一部分,並用你選擇的語言爲你生成它。

看看它,它有一個例子,它的常見問題解釋 - https://txt2re.com/index.php3?d=faq