2016-07-29 11 views
1

の解析速度を向上させる私はは、区切られた構成ブロック

#start <some-name> ... #end <some-name>で区切られたブロックで構成され、非常に大きな設定ファイルを持っているsome-nameたブロックで同じである必要があります。ブロックは複数回出現することができますが、決してそれ自身の中には含まれません。特定のブロックには他のブロックだけが表示されることがあります。私はこれらのブロックに興味がありませんが、第2レベルのブロックには興味があります。

実際のファイルでは、名前はblockXで始まらず、お互いに非常に異なっています。

例:

#start block1 

    #start block2 

    /* string but no more name2 or name1 in here */ 
    #end block2 

    #start block3 
    /* configuration data */ 
    #end block3 

#end block1 

これは正規表現で解析され、非常に高速、付属のデバッガなしで実行すると、あるされています。以下のような単純なルールに 2kの 2.7メガバイトのファイルのための0.23s:

blocks2 = re.findAll('#start block2\s+(.*?)#end block2', contents) 

私はpyparsingでこれを解析しようとしたが、速度も添付デバッガなしで非常に遅いですが、それは同じファイルに対して16秒を要しました。

私のアプローチは、正規表現からの単純な解析を模倣するような標準的なコードを生成することでしたので、今は他のコードを使用して、今すぐすべてのブロックを解析する必要はありません。文法はかなり拡張されています。ここで

は、私は私が間違っているのは何

block = [Group(Keyword(x) + SkipTo(Keyword('#end') + Keyword(x)) + Keyword('#end') - x)(x + '*') for x in ['block3', 'block4', 'block5', 'block6', 'block7', 'block8']] 

blocks = Keyword('#start') + block 

x = OneOrMore(blocks).searchString(contents) # I also tried parseString() but the results were similar. 

を試してみました何ですか?どのように正規表現の実装で達成される速度に近いところに来るようにこれを最適化できますか?

編集:前の例では、実際のデータに比べて簡単に行く途中だったので、私は今、適切なものを作成した。このため

/* all comments are C comments */ 
VERSION 1 0 
#start PROJECT project_name "what is it about" 
    /* why not another comment here too! */ 
    #start SECTION where_the_wild_things_are "explain this section" 


     /* I need all sections at this level */ 

     /* In the real data there are about 10k of such blocks. 
      There are around 10 different names (types) of blocks */ 


     #start INTERFACE_SPEC 
     There can be anything in the section. Not Really but i want to skip anything until the matching (hash)end. 
     /* can also have comments */ 

     #end INTERFACE_SPEC 

     #start some_other_section 
      name 'section name' 

      #start with_inner_section 
       number_of_points 3 /* can have comments anywhere */ 
      #end with_inner_section 
     #end some_other_section /* basically comments can be anywhere */ 

     #start some_other_section 
      name 'section name' 
      other_section_attribute X 
      ref_to_section another_section 
     #end some_other_section 

     #start another_section 
      degrees 
      #start section_i_do_not_care_about_at_the_moment 
       ref_to some_other_section 
       /* of course can have comments */ 
      #end section_i_do_not_care_about_at_the_moment 
     #end another_section 

    #end SECTION 
#end PROJECT 

を私は自分のオリジナルの提案を展開していました。私は外側の2つのブロック(PROJECTとSECTION)をハードコーディングしています。

def test_parse(f): 
     import pyparsing as pp 
     import io 
     comment = pp.cStyleComment 

     start = pp.Literal("#start") 
     end = pp.Literal("#end") 
     ident = pp.Word(pp.alphas + "_", pp.printables) 

     inner_ident = ident.copy() 
     inner_start = start + inner_ident 
     inner_end = end + pp.matchPreviousLiteral(inner_ident) 
     inner_block = pp.Group(inner_start + pp.SkipTo(inner_end) + inner_end) 

     version = pp.Literal('VERSION') - pp.Word(pp.nums)('major_version') - pp.Word(pp.nums)('minor_version') 

     project = pp.Keyword('#start') - pp.Keyword('PROJECT') - pp.Word(pp.alphas + "_", pp.printables)(
       'project_name') - pp.dblQuotedString + pp.ZeroOrMore(comment) - \ 
       pp.Keyword('#start') - pp.Keyword('SECTION') - pp.Word(pp.alphas, pp.printables)(
       'section_name') - pp.dblQuotedString + pp.ZeroOrMore(comment) - \ 
       pp.OneOrMore(inner_block) + \ 
       pp.Keyword('#end') - pp.Keyword('SECTION') + \ 
       pp.ZeroOrMore(comment) - pp.Keyword('#end') - pp.Keyword('PROJECT') 

     grammar = pp.ZeroOrMore(comment) - version.ignore(comment) - project.ignore(comment) 

     with io.open(f) as ff: 
       return grammar.parseString(ff.read()) 

EDIT::時間は〜16秒のままであるこのバージョンで

タイプミスは、それが2Kと言ったが、それは代わりに、2.7メガバイトのファイルです。すべての

答えて

1

まず、私のために動作しません掲載され、このコード:

blocks = Keyword('#start') + block 

これに変更:

blocks = Keyword('#start') + MatchFirst(block) 

は、少なくともあなたのサンプルテキストに対して実行されます。

ではなく、ハードコードすべてのキーワード、あなたはpyparsingの適応式のいずれか、matchPreviousLiteralを使用して試すことができます:

(編集)

def grammar(): 
    import pyparsing as pp 
    comment = pp.cStyleComment 

    start = pp.Keyword("#start") 
    end = pp.Keyword('#end') 
    ident = pp.Word(pp.alphas + "_", pp.printables) 
    integer = pp.Word(pp.nums) 

    inner_ident = ident.copy() 
    inner_start = start + inner_ident 
    inner_end = end + pp.matchPreviousLiteral(inner_ident) 
    inner_block = pp.Group(inner_start + pp.SkipTo(inner_end) + inner_end) 

    VERSION, PROJECT, SECTION = map(pp.Keyword, "VERSION PROJECT SECTION".split()) 

    version = VERSION - pp.Group(integer('major_version') + integer('minor_version')) 

    project = (start - PROJECT + ident('project_name') + pp.dblQuotedString 
       + start + SECTION + ident('section_name') + pp.dblQuotedString 
       + pp.OneOrMore(inner_block)('blocks') 
       + end + SECTION 
       + end + PROJECT) 

    grammar = version + project 
    grammar.ignore(comment) 

    return grammar 

ignore()を呼び出すために必要なだけですあなたの文法の一番上の表現 - それはすべての内部表現に伝播します。また、すでにignore()を呼び出している場合は、文法にZeroOrMore(comment)を振りかざす必要はありません。

2KBの入力文字列(10,000ブロックの内部ブロックを含む)を約16秒間に解析したので、2Kファイルの長さは約1/1000になります。

+0

'matchPreviousLiteral'のヒントをありがとう。他の機能と同様に、http://pyparsing.wikispaces.com/HowToUsePyparsingには記載されていません。それ以上の最新のドキュメントはありますか? – RedX

+0

https://pythonhosted.org/pyparsing/で、各リリースのオンラインドキュメントを最新の状態に保ちます。 http://infohost.nmt.edu/tcc/help/pubs/pyparsing/web/index.htmlのJohn Shipmanのページも参考になりますが、少し意見があります。文法のクリーンアップに関する推奨事項については、上記の私の編集を参照してください。 – PaulMcG

+0

私が最初に述べたファイルサイズは1000だけずれていました.2kBではなく、最初から2.7MBでした。申し訳ありません。私は、このプロジェクトでは、より良い結果が得られるはずですが、時間差はあまりにも大きいので、私はこのプロジェクトでは、pyparsingを使用することができなくなると思います。 – RedX

関連する問題