53

目標

今日のコードゴルフの課題は、できるだけ少ない文字で正規表現パーサーを作成することです。

構文

いいえ、Perlスタイルの正規表現に一致するように求めているわけではありません。結局のところ、それらのための非常に信頼できるインタプリタがすでにあります!:-)

このチャレンジの正規表現構文について知っておく必要があるのは次のとおりです。

  • 用語は、単一のリテラル文字、またはグループ化括弧内の正規表現として定義されます()
  • *アスタリスク)文字は、前のTERMでのクリーネ閉包操作を表します。これは、前の用語が0個以上連結されていることを意味します。
  • +プラス)文字は便利なショートカットを表します。は、前の用語の1つ以上を意味するa+と同等です。aa*
  • ?疑問符)文字は、前の用語の0または1つを表します。
  • (パイプ)文字は交互を表します。|つまり、どちらの側の正規表現も試合で使用できます。
  • 他のすべての文字はリテラルであると見なされます。他のすべての文字が中にあると想定することができます[0-9A-Za-z](つまり、すべての英語の英数字)。

または、別の言い方をすれば、 *//優先順位が最も高く、次に連結、次に交互になります。交互は連結よりも優先順位が低いため、括弧なしで正規表現内で使用すると、両側で完全な正規表現にバインドされます。一方、およびは、直前の用語にのみ適用されます。+?*+?

チャレンジ

あなたの課題は、正規表現(上記で定義されている)をコンパイルまたは解釈し、それに対していくつかの文字列をテストするプログラムを作成することです。

入力はあなたに任せます。私の推奨事項は、おそらく正規表現が最初に来て、次にそれに対してテストされる任意の数の文字列が来ることです。しかし、それを長持ちさせたいのであれば、それは問題ありません。すべてをコマンドライン引数またはstdinに入れたい場合、またはコマンドラインに正規表現を入れてstdinに文字列を入れたい場合など、それで問題ありません。使用例を1つか2つ示してください。

正規表現が一致するかどうかを反映するために、出力はtrueまたはfalse、1行に1つである必要があります。

ノート:

  • 私はこれを言う必要はありません...しかし、あなたの言語で正規表現ライブラリを使用しないでください!パターンを自分でコンパイルまたは解釈する必要があります。(編集:文字列の分割または結合に必要な場合は、正規表現を使用できます。たとえば、入力正規表現を言語正規表現に変換して使用するなど、問題を直接解決するために使用することはできません。)
  • 正規表現は、このチャレンジの入力文字列と完全に一致する必要があります。(同様に、Perlのような正規表現に精通している場合は、すべての一致に対して文字列の開始と終了のアンカーが設定されていると想定します)
  • このチャレンジでは、すべての特殊文字()*+?|が文字通り出現することは期待されていません。入力に出てきた場合、問題の文字列に一致するパターンはないと想定しても安全です。
  • テストする入力文字列は、大文字と小文字を区別して評価する必要があります。

例では、すべてがコマンドライン引数で行われると想定しています。最初に正規表現を使用します。(上で述べたように、入力はあなた次第です。)myregexここで、プログラムの呼び出しを表します。

> myregex easy easy Easy hard
true
false
false

> myregex ab*a aa abba abab b
true
true
false
false

> myregex 0*1|10 1 10 0110 00001
true
true
false
true

> myregex 0*(1|1+0) 1 10 0110 00001
true
true
true
true

> myregex a?b+|(a+b|b+a?)+ abb babab aaa aabba a b
true
true
false
true
false
true

注:申し訳ありませんが、コミュニティwikiを作成するのを忘れました!:-(

4

7 に答える 7

20

C (通訳)、630 622 617 615 582 576 573 572 558 554 544 538 529 504 502 500 499 文字

これは括弧を理解し、正規表現ライブで動作します (つまり、最初に解析されません)。

#define C char
#define M m(s,o
m(C*s,C*o,C*S,C*p,C*P,C T){C*n=P-1,*q=s,h=*P==41,c=1;for(;h*c;c-=*n--==40)c+=*n==41;
c=*P-42;c=p-P?c-82?T&&c&~1&&c-21?h?2:*S==*P&s<S?M,S-1,p,n,2)||(T&4&&M,S-1,p,P,T|1)):
4:M,T?S:o,p,P-1,T?c&~1?3:7-c:0):T&&s==S||M,o,p,P-1,2):T&&s==S;if(c==2)for(c=4;q<=S;q
++)c|=m(q,S,S,n+h,P-h,2)?M,q,p,n,2)||q<S&T/4&&M,q,p,P,T&6):0;return
c&4?c&1?:T&1&&M,S,p,n,2)||M,o,p,n,0):c;}main(C*w,C**v){C*u;for(w=*++v;*++v;)puts(m(*v
-1,u,u=index(*v,0)-1,w-1,index(w,0)-1,2)?"true":"false");}

-Wall を指定してコンパイルすると、argc が char であるというエラーが表示されます。不正なパターンでクラッシュします。

これにより、正規表現と文字列が右から左に解析され、数文字が節約されます。

argv で入力し、の通常の順序で出力します。

$ ./a.out ab*a aa abba abab b
true
true
false
false

$ ./a.out "0*1|10" 1 10 0110 00001
true
true
false
true

$ ./a.out "0*(1|1+0)" 1 10 0110 00001
true
true
true
true

$ ./a.out "a?b+|(a+b|b+a?)+" abb babab aaa aabba a b
true
true
false
true
false
true

$ ./a.out "((A|B|C)+(a|(bbbbb)|bb|c)+)+" ABCABCaccabbbbbaACBbbb
false
$ ./a.out "((A|B|C)+(a|(bbbbb)|bb|c)+)+" ABCABCaccabbbbbaACBbbbb
true
于 2010-08-20T02:43:13.697 に答える
15

GolfScript-254文字

n%([]:B:$:_"()"@*{:I"()*+|?"[{}/]?[{[[0B$,+:B))\;)]_]+}{B)):ß;:B;qß(:ß;}{8q}{[[0ß0$,)]]+}:8{[[0B-1=:ß)]]+:$q}{ß>$ß<\([0+$,+]\++}:q{[[I$,:ß)]]+}]=~:$}/;{n+[0]:3\{:c;;3:1_:3;{,}{)[$=]_*2/{~\.{c={3|:3}*;}{;.1|1,\:1,<{+0}*;}if}/}/;}/;1$,?)"true""false"if n}%

やや簡単に、最初のループは正規表現をNFAに変換し、2番目のループが実行されます。

Sun Aug 22 00:58:24 EST 2010 (271→266)変数名を変更してスペースを削除
Sun Aug 22 01:07:11 EST 2010 (266→265)[]変数を作成
Sun Aug 22 07:05:50 EST 2010 (265→259)ヌル状態遷移をインライン
Sun Aug 22 07:19:21 EST 2010 (259→256)最終状態を暗黙
Mon Feb 7 19:24:19 EST 2011 (256→254)使用"()""str"*

$ echo "ab*a aa abba abab b"|tr " " "\n"|golfscript regex.gs
true
true
false
false

$ echo "0*1|10 1 10 0110 00001"|tr " " "\n"|golfscript regex.gs
true
true
false
true

$ echo "0*(1|1+0) 1 10 0110 00001"|tr " " "\n"|golfscript regex.gs
true
true
true
true

$ echo "a?b+|(a+b|b+a?)+ abb babab aaa aabba a b"|tr " " "\n"|golfscript regex.gs
true
true
false
true
false
true

$ echo "((A|B|C)+(a|(bbbbb)|bb|c)+)+ ABCABCaccabbbbbaACBbbb ABCABCaccabbbbbaACBbbbb"|tr " " "\n"|golfscript regex.gs
false
true
于 2010-08-21T14:40:50.867 に答える
7

ハスケル: 610 612 627

main=getLine>>=f.words
d=reverse
u=0<1
j=[]
f(r:s)=mapM_(print.any null.c(d$b$'(':r++")"))s
c%(x,y)=(c:x,y)
s _ _ _[]=(j,j)
s n a b (x:y)|n<1&&x==a=(j,x:y)|x==a=f(-1)|x==b=f 1|u=f 0where f k=x%s(n+k)a b y
b r|m==j=r|u=b$d(o++"(("++x)++")("++z++")/)"++w where(c,m)=s 0'|''!'r;_:v=m;(o,_:x)=s 0'('')'$d c;(z,_:w)=s 0')''('v
(!)g f x=f x>>=g
c[]=(:j)
c r=f!c s where(s,f)=i r
p q@(r:s)|r=='('=(s,(:j))|u=(a,f!g)where(w,f)=i q;(a,g)=p w
_?[]=j
c?(h:r)|c==h=[r]|u=j
i(r:q)=maybe(q,(r?))id$lookup r$zip")/*+?"$p q:zip[e,w,w,w][\s->f s++g s,\s->s:l s,l,\s->s:f s]where(w,f)=i q;(e,g)=i w;l s|f s==j=j|u=f s++(f s>>=l)

非ゴルフ:

import Control.Monad
import Data.List

-- (aa|bb|cc)   -->  )|)cc()|)bb()aa((( 

testRegex = "a?b+|(a+b|b+a?)+"

interpret regex = any null . interpret' regex

interpret' regex = compile (rewrite regex)

mapFst f (x, y) = (f x, y)

splitWhileBalanced _ _ _ "" = ("", "")
splitWhileBalanced n b1 b2 (x:xs)
  | n < 1 && x == b1 = ("", x:xs)
  | x == b1   = f (-1)
  | x == b2   = f 1
  | otherwise = f 0
  where
    f k = mapFst (x:) $ splitWhileBalanced (n+k) b1 b2 xs

rewrite regex = reverse $ moveBars $ '(' : regex ++ ")"

moveBars regex
  | mtBar == "" = regex
  | otherwise = moveBars $ reverse (hOpen ++ "((" ++ tOpen) ++ ")(" ++ hClose ++ ")/)" ++ tClose
  where
    (hBar, mtBar) = splitWhileBalanced 0 '|' '!' regex -- '!' is a dummy character
    b:tBar = mtBar
    (hOpen, o:tOpen) = splitWhileBalanced 0 '(' ')' $ reverse hBar
    (hClose, c:tClose) = splitWhileBalanced 0 ')' '(' $ tBar

compile "" = \x -> [x]
compile rs = f <=< compile rs'
  where 
    (rs', f) = compile' rs

paren regex@(r:rs0)
  | r == '('  = (rs0, \x -> [x])
  | otherwise = (rs2, f <=< g)
  where
    (rs1, f) = compile' regex
    (rs2, g) = paren rs1

compile' (r:rs0) = case r of
  '/' -> (rs2, bar)
  '*' -> (rs1, star)
  '+' -> (rs1, plus)
  '?' -> (rs1, mark)
  ')' -> paren rs0
  _   -> (rs0, lit)
  where
    (rs1, f) = compile' rs0
    (rs2, g) = compile' rs1
    bar str = f str ++ g str
    plus str
      | null (f str) = []
      | otherwise = f str ++ (f str >>= plus)
    star str = str : plus str
    mark str = str : f str
    lit = maybe [] (\x -> [x]) . stripPrefix [r]

メモ:

接尾辞形式に変更|してから、正規表現全体を逆にします。演算子はプレフィックス形式になり、解析が容易になりました。プログラムは正規表現を次のように解析します。リストモナドは非決定性のために使用されます。

使用法:

> runghc Regex.hs
a?b+|(a+b|b+a?)+ abb babab aaa aabba a b
True
True
False
True
False
True
于 2010-08-22T05:31:00.410 に答える
7

C (解析 + 照合) 727 670 文字

これはまだ最低限のことを完全に把握しているわけではありませんが、最初に正規表現を解析することが実際の解釈に違いをもたらすかどうかを試してみたいと思いました. 解析と照合の両方を記述/理解するのは簡単ですが、コストがかかるためです。

#define Q struct q
#define C char
#define R return
Q{Q*u,*n,*a;C c,m};Q*P(C*p,C*e){Q*r=calloc(99,1);C*n=p+1,c=1,w;if(p==e)R
r;if(*p==40){for(;c;)c+=(*n==40)-(*n++==41);r->u=P(p+1,n-1);}else
if(*p=='|'){r->a=P(p+1,e);R r;}else r->c=*p;if(n<e){if(*n==43)*n=42,r->n=P(p,e);else
w=*n==42|*n==63,r->n=P(n+w,e),r->m=w?*n:0;r->a=r->n->a;}R r;}M(Q*r,C*s,C*o,C*z){C*p,
e;e=r?r->m==63|r->m==42&&M(r->n,s,o,z)?:*s&&r->c==*s?M(r->m==42?r:r->n,s+1,o,z):2:s
==z;if(e-2)R e;for(p=s,e=0;!r->c&p<=z;p++)e|=M(r->u,s,s,p)&(r->m!=42|p>s)&&M(r->m==
42?r:r->n,p,p,z);R e||r->a&&M(r->a,o,o,z);}main(C
c,C**v){for(;--c>1;)puts(M(P(v[1],index(v[1],0)),v[c],v[c],index(v[c],0))?"true":"false");}

正規表現を構造体に解析します。各構造体には次のものがあります。

  • 照合する文字、または照合するサブパターンの構造体へのポインタ
  • 次のシンボル構造体へのポインタ (存在する場合)
  • 次の代替パターンへのポインター (存在する場合)
  • \0*または?--である乗数はすぐに(pat)+解析され、マッチングがはるかに簡単になります。(pat)(pat)*
于 2010-08-21T20:16:32.320 に答える
5

Ruby 1.9: 709 文字

R=gets.chop;s='';k=[];n=a=0
G={?(=>(A="(a-=1;s<<0)if a>1;")+"k<<[n,a];n=a=0",
Y=?|=>(B="s<<0while 0<a-=1;")+"n+=1",
?)=>B+(C="s<<?|while 0<=n-=1;")+"n,a=k.pop"+F=";a+=1",
?*=>D="s<<c",?+=>D,??=>D}
R.each_char{|c|eval G[c]||A+D+F};eval B+C
def P l,s;l.map{|a|a<<s};end
J={??=>(K="a=k.pop;")+"k<<[{Y=>n=[a[0]]},a[1]<<n]",
?*=>K+(H="P a[1],s={Y=>n=[a[0]]};")+"k<<[s,[n]]",
?+=>K+H+"k<<[a[0],[n]]",
Y=>(I=K+"b=k.pop;")+"k<<[{Y=>[a[0],b[0]]},a[1]+b[1]]",
?\0=>I+"P b[1],a[0];k<<[b[0],a[1]]"}
k=[];s.each_char{|c|eval J[c]||"k<<[{c=>a=[]},[a]]"}
e=k[0];P e[1],R;
p $<.map{|l|s=l.chop;*a=e[0]
s.each_char{|c|eval@f="n=a;a=a.map{|h|h[Y]||[h]}.flatten"while a!=n
a=a.inject([]){|a,h|a+(h[c]||[])}}
eval@f;a.include? R}

(以下のエイリアスを追加することで、Ruby 1.8 でも 45 文字追加で動作します)

でテストtype testcase.txt | ruby regex.rb

RubyでのRuss Cox の NFA パーサーの実装。状態は、次の状態の配列を保持する単一のキーを持つハッシュとして表されます。

非ゴルフ:

#needed to run on ruby 1.8
class String
  alias :each_char :each_byte 
end  

## Infix to Postfix

R=gets.chop  
p "regexp = #{R}"
k=[] 
s=''  #will hold postfix string
n=a=0 #count braNches and Atoms
postfix = R.each_char{|c|
  case c
    when ?(
        (a-=1;s<<0)if a>1
        k<<[n,a]
        n=a=0
    when ?|
      s<<0while 0<a-=1
      n+=1
    when ?)
      s<<0while 0<a-=1
      s<<?|while 0<=n-=1
      n,a=k.pop;a+=1
    when ?*,?+,??
      s<< c
    else
      (a-=1;s<<0)if a>1
      s<< c
      a+=1
  end
}
s<<0while 0<a-=1
s<<?|while 0<=n-=1

## Postfix to NFA
# State is {char=>s0=[state,state]]
# Frag is [state, [s0,...]]

Y=?|   #choice indicator
X={:true=>[R]} #matcstate

 def patch l,s   #l is list of arrays, s is state.  Put s in each array
   l.map{|a|   a<< s   }
end

k=[]
s.each_char{|c|
 case c
    when ??
      a=k.pop
      s={Y=>n=[a[0]]}
      k<<[s,a[1]<<n]
    when ?*
      a=k.pop
      s={Y=>n=[a[0]]}
      patch(a[1],s)
      k<<[s,[n]]
    when ?+
      a=k.pop
      s={Y=>n=[a[0]]}
      patch(a[1],s)
      k<<[a[0],[n]]
    when ?|
     b=k.pop
     a=k.pop
      s={Y=>[a[0],b[0]]}
      k<<[s,a[1]+b[1]]
    when 0
     b=k.pop
     a=k.pop
     patch(a[1],b[0])
     k<<[a[0],b[1]]
   else
     k<<[{c=>a=[]},[a]]
   end
}
e=k.pop
patch(e[1],X)

#Running the NFA

E=[e[0]] #Evaluator
p $<.map{|l|s=l.chop
  p "evaluating: #{s}" 
  a=E
  s.each_char{|c|
    begin     #skip past split nodes
      s=a.size
      a=a.map{|h|h[?|]||[h]}.flatten  
    end while a.size!=s
    a=a.inject([]){|a,h|
    a+(h[c]||[])}  #add next state or null
  }
  a=a.map{|h|h[?|]||[h]}.flatten
  r = a.include? X  #check for end of pattern
  p r
  r
}
于 2010-08-26T08:36:01.493 に答える
5

Perl、596 文字

半説明:

  • これは、 Higher Order Perlの第 8 章にある継続渡しスタイルのパーサー コンビネータに基づいています。
  • 約 70 文字の削除を手伝ってくれた功績は、Vincent Pit (VPIT) に送られます。
  • S { block }sub { block }毎回2文字短くなるだけと同じです。
  • $,nil (常に一致し、何も消費しないマッチャーを含むコードリファレンス)
  • cn-ary 連結です (任意の数のマッチャーを取得し、それらがすべて順番に成功した場合に成功するマッチャーを返します)。
  • an-ary オルタネーションです (任意の数のマッチャーを取り、それらのいずれかが成功した場合に成功するマッチャーを返します)。
  • A正規表現ビルダーのヘルパーです — 連結の交互の構造を取り、C必要aに応じてマッチャーを返します。
  • kスターです (マッチャーを取得し、連続して 0 回以上一致するマッチャーを返します。Kleene の場合、 は演算子として解析されるkため:)s()s///
  • doブロックは一度に 1 文字ずつ正規表現を解析します。@$rは現在の連結リスト、@aは現在の代替リスト、@pは親グループ スタックです。a+は として扱われaa*、inline ina?として扱われます (またはの関数はありません)。(a|)bplusmaybe
  • S{!length pop}最後の行の は、入力の最後で成功するマッチャーです。これは、正規表現マッチャーの継続として渡されます。つまり、正規表現は、文字列全体と一致する場合にのみ成功します。

ほとんどが degolfed で、より多くのコメントが付けられたコードについては、この Gistを参照してください。

として実行しますperl regexer.pl 'a?b+|(a+b|b+a?)+' abb babab aaa aabba a b。コードに必須の改行はありません。

use feature':5.12';
sub S(&){pop}
$,=S{goto pop};
sub p{push@{+shift},@_}
sub c{my$l=$,;for my$r(@_){my$L=$l;
$l=S{my($i,$c)=@_;&$L($i,S{&$r(shift,$c)})}}$l}
sub a{my@A=@_;S{my($i,$c,$o)=@_;$o=&$_($i,$c)and return$o for@A;0}}
sub A{$#_?a(map c(@$_),@_):c(@{+pop})}
sub k{my($I,$k)=@_;$k=a c($I,S{&$k}),$,}
$_=shift;$P=do{@a=$r=[];for(/./g){
when('('){p\@p,[@a];@a=$r=[]}
when(')'){$p=A@a;@a=@{pop@p};p$r=$a[-1],$p}
p\@a,$r=[]when'|';
p$r,k pop@$r when'*';
p$r,c $$r[-1],k pop@$r when'+';
p$r,a pop@$r,$,when '?';
my$s=$_;p$r,S{my($_,$c)=@_;s/^\Q$s//&&$_->$c}}A@a};
say&$P($_,S{!length pop})?"true":"false"for@ARGV
于 2010-08-23T09:03:01.797 に答える
4

JavaScript、658 文字

// All whitespace is optional
function c(f,p){
    var x=f[0],w=p[0],h="substr",s=f[h](2),b=p[h](1),m=0,t=0,r,g,a=0,l,u="length",y="*";
    switch(f[1]){
        case"+":if(x!=w)return;f=x+y+s;
        case y:return x==w&&c(f,b)||c(s,p);
        case"?":return x==w&&c(s,b)||c(s,p)
    }
    if(x=="("){
        o:for(l=[""];t<f[u];t++){
            switch(f[t]){
                case"|":if(a==1){m=l.push("")-1;continue}break;
                case"(":if(++a==1)continue;break;
                case")":if(!--a)break o
            }
            l[m]+=f[t]
        }
        var v=0,e=t+1;
        return l.some(function(g){
            switch(f[t+1]){
                case y:v=1;
                case"+":e=t+2;
                    for(var q=0;q<f[u];q++)
                        if(c(g+Array(q).join(f[h](0,e))+f[h](e),p))
                            return 1;
                    return;
                case"?":v=1;e=t+2;default:if(c(g+f[h](e),p))return 1;
            }
        })||(v&&c(f[h](e),p))
    }
    return p[u]?(x==w&&c(f[h](1),b)):!f[u]
}

// Make it look nicer
function test(regex, string) { return !!c('(' + regex + ')', string); }

test('a?b+|(a+b|b+a?)+', 'abb') // true
test('a?b+|(a+b|b+a?)+', 'babab') // true

アンゴルフ、〜1500文字

function test(reg, str) {
    console.log('Testing ' + reg + ' against ' + str);
    var c = reg[0], d = str[0];


    switch (reg[1]) {
        case '+': 
            if (c != d) 
                return false;   
            reg = c + '*' + reg.substr(2);
        case '*': 
            return (c == d && test(reg, str.substr(1))) || test(reg.substr(2), str);
        case '?': 
            return (c == d && test(reg.substr(2), str.substr(1))) || test(reg.substr(2), str);
    }

    if (c == '(') {
        var regs = [''];
        o: for (var level = n = i = 0; i < reg.length; i++) {
            //console.log(level + ': ' + n + ': ' + reg[i]);
            switch (reg[i]) {
                case '|': 
                    if (level == 1) { n = regs.push('') - 1; continue; } 
                    break;
                case '(': 
                    if (++level == 1) continue; 
                    break;
                case ')': 
                    if (!--level) break o; 
                    break;
            };
            regs[n] += reg[i];
        }
        //console.log(regs); // An array of alternates (hello|hi) => ['hello', 'hi']        

        var optional = false, end = i+1;
        return regs.some(function(jitem) {
            switch (reg[i+1]) {
                case '*': optional = true; // Fall through
                case '+': 
                    end = i+2;
                    for (var k = 0; k < reg.length; k++)
                        if (test(jitem + Array(k).join(reg.substr(0, i+2)) + reg.substr(i+2), str))
                            return true;
                    return false;

                case '?': optional = true; end = i+2; // Fall through
                default: if (test(jitem + reg.substr(end), str)) return true;       
            }

        }) || (optional && test(reg.substr(end), str));
    }

    if (str == '')
        return reg == '';

    return c == d ? test(reg.substr(1), str.substr(1)) : false;

}

これは、正規表現の先頭と文字列の先頭を切り取って再帰し、それ自体を呼び出すことによって機能します。たとえば、test("hello", "hello") => test("ello", "ello") => test("llo", "llo") => test("lo", "lo") => test("o", "o") => test("", "")true を返します。注: ベアc関数は暗黙の代替をサポートしません。つまり、機能しhello|hiません。括弧で囲む必要があります: (hello|hi).

于 2010-08-24T01:00:23.903 に答える