1

化学元素のデータベースのクエリを解析したいと思います。

データベースは xml ファイルに保存されます。そのファイルを解析すると、collections.OrderedDict から継承するシングルトン オブジェクトに格納されるネストされた辞書が生成されます。

要素を要求すると、対応するプロパティの順序付けられた辞書が得られます (つまり、ELEMENTS['C'] --> {'name':'carbon','neutron' : 0,'proton':6, ...} )。

逆に、プロパティを要求すると、すべての要素の値の順序付けられた辞書が得られます (つまり、ELEMENTS['proton'] --> {'H' : 1, 'He' : 2} ...)。

典型的なクエリは次のとおりです。

mass > 10 or (nucleon < 20 and atomic_radius < 5)

ここで、各「サブクエリ」(つまり、質量 > 10) は、それに一致する要素のセットを返します。

次に、クエリが変換され、内部的に文字列に変換されます。この文字列はさらに評価され、一致した要素の一連のインデックスが生成されます。そのコンテキストでは、演算子 and/or はブール演算子ではなく、Python セットに作用するアンサンブル演算子です。

私は最近、そのようなクエリを作成するための投稿を送信しました。私が得た有用な回答のおかげで、多かれ少なかれ仕事をしたと思います (良い方法であるといいのですが!) が、パイパーシングに関連するいくつかの質問がまだあります。

これが私のコードです:

import numpy

from pyparsing import *

# This import a singleton object storing the datase dictionary as
# described earlier
from ElementsDatabase import ELEMENTS

and_operator = oneOf(['and','&'], caseless=True) 
or_operator  = oneOf(['or' ,'|'], caseless=True) 

# ELEMENTS.properties is a property getter that returns the list of 
# registered properties in the database
props = oneOf(ELEMENTS.properties, caseless=True)

# A property keyword can be quoted or not.
props = Suppress('"') + props + Suppress('"') | props
# When parsed, it must be replaced by the following expression that 
# will be eval later.
props.setParseAction(lambda t : "numpy.array(ELEMENTS['%s'].values())" % t[0].lower())

quote = QuotedString('"')
integer = Regex(r'[+-]?\d+').setParseAction(lambda t:int(t[0]))
float_  = Regex(r'[+-]?(\d+(\.\d*)?)?([eE][+-]?\d+)?').setParseAction(lambda t:float(t[0]))

comparison_operator = oneOf(['==','!=','>','>=','<', '<='])
comparison_expr = props + comparison_operator + (quote | float_ | integer)
comparison_expr.setParseAction(lambda t : "set(numpy.where(%s)%s%s)" % tuple(t))

grammar = Combine(operatorPrecedence(comparison_expr, [(and_operator, 2, opAssoc.LEFT) (or_operator, 2, opAssoc.LEFT)]))

# A test query
res = grammar.parseString('"mass     "  >  30 or (nucleon == 1)',parseAll=True)

print eval(' '.join(res._asStringList()))

私の質問は次のとおりです。

1 using 'transformString' instead of 'parseString' never triggers any 
  exception even when the string to be parsed does not match the grammar. 
  However, it is exactly the functionnality I need. Is there is a way to do so ?

2 I would like to reintroduce white spaces between my tokens in order 
that my eval does not fail. The only way I found to do so it the one 
implemented above. Would you see a better way using pyparsing ?

長い投稿で申し訳ありませんが、そのコンテキストをより詳細に紹介したかった. ところで、このアプローチが悪いと思ったら、遠慮なく教えてください!

ご助力ありがとうございます。

エリック

4

1 に答える 1

1

私の懸念を心配しないでください、私は回避策を見つけました。pyparsingに同梱されているSimpleBool.pyの例を使用しました(ヒントPaulに感謝します)。

基本的に、私は次のアプローチを使用しました。

1 for each subquery (i.e. mass > 10), using the setParseAction method, 
I joined a function that returns the set of eleements that matched 
the subquery

2 then, I joined the following functions for each logical operator (and, 
or and not):

def not_operator(token):

    _, s = token[0]

    # ELEMENTS is the singleton described in my original post
    return set(ELEMENTS.keys()).difference(s)

def and_operator(token):

    s1, _, s2 = token[0]

    return (s1 and s2)

def or_operator(token):

    s1, _, s2 = token[0]

    return (s1 or s2)

# Thanks for Paul for the hint.
grammar = operatorPrecedence(comparison_expr,
          [(not_token, 1,opAssoc.RIGHT,not_operator),
          (and_token, 2, opAssoc.LEFT,and_operator),
          (or_token, 2, opAssoc.LEFT,or_operator)])

Please not that these operators acts upon python sets rather than 
on booleans.

そしてそれは仕事をします。

このアプローチが皆さんのお役に立てば幸いです。

エリック

于 2012-11-03T15:12:13.147 に答える