• 百姓网那道题


    赖勇浩(http://laiyonghao.com

    昨天 @smallfish 传了个网址(http://home.wangjianshuo.com/cn/20100505_ccceeieeaece.htm )给我,打开一看,是王建硕先生的博客,正好我前段时间经由阮一峰的博客看到过他的简介,就好奇他又写了啥。仔细一看,原来是出了一道公开的笔试题,蛮有意思的,就决心做做看。

    一开始看到要支持 AND OR 等逻辑操作符,想来也要支持括号标明优先级了,就想到拿前几天 python-cn 社区介绍的 PLY 模块做个 parser。虽然是第一次用这个模块,但真的很好用,写 parser 用的时间很短,调试了几下,就能够支持比较复杂的表达式了,比如:

    (age < 15 and sex == 0) or age > 30

    整出来的语法树如下:
    or
      and
        ('<', 'age', 15)
        ('==', 'sex', 0)
      ('>', 'age', 30)

    接下来在比较两棵树是否有子集关系的时候,遇到了问题。今天早上看到 qmigh(http://qmigh.blogspot.com/ )的想法,启发了我。不过他提出的方案是把查询表达式“规范化”,这一步是比较难做的,需要用到离散数学的一些知识,实现起来比较烦。我思考之后,发现“规范化”只是为了将语法树转成两层的或与树,想到这一点之后,就可以不进行“规范化”了,只要将与或树的特性应用到语法树中即可。具体的判断方案我在代码里以注释的形式写了出来。

    本题的解决方案后面的知识,SeasonLee 有一篇文章写得清晰,可供参考:从离散数学到编译原理--百姓网编程题后序(http://www.cnblogs.com/SeasonLee/archive/2010/05/11/1731414.html

    现在这个方案可以实现比较复杂的表达式判断,不过不支持 NOT,也不支持 != 号,只支持 AND、OR、>、< 和 ==,可以使用 () 指定优先级。其实代码不长,约有 100 行是后面测试用的一些代码。

    经王建硕先生同意,我把自己的方案公布到博客上,欢迎大家讨论和指正。

      1 # encoding:utf-8
      2
      3 try :
      4   from  ply import  yacc
      5   from  ply import  lex
      6 except  ImportError:
      7   import  sys
      8   print  "Please install PLY first. "
      9   print  r"You can download it from here: http://www.dabeaz.com/ply/ply-3.3.tar.gz "
     10   sys.exit(1)
     11
     12 #############################################
     13 #                lex
     14 #############################################
     15
     16 # 定义条件表达式的词法
     17 tokens = ['FIELD ', 'NUMBER ', 'GT ', 'EQ ', 'LT ', 'AND ', 'OR ', 'LPAREN ', 'RPAREN ']
     18
     19 t_ignore = '  /t '
     20 t_FIELD = r'(?!and)(?!or)[a-zA-Z_][a-zA-Z0-9_]* '
     21 t_GT = r'> '
     22 t_EQ = r'== '
     23 t_LT = r'< '
     24 t_AND = r'and '
     25 t_OR = r'or '
     26 t_LPAREN = r'/( '
     27 t_RPAREN = r'/) '
     28
     29 def  t_NUMBER (t):
     30   r'/d+ '
     31   t.value = int(t.value)
     32   return  t
     33
     34 def  t_error (t):
     35   raise  TypeError("Unknow text '%s' "%t.value)
     36
     37 lex.lex()
     38
     39 #############################################
     40 #                yacc
     41 #############################################
     42
     43 # 用 BNF 定义条件表达式的语法,支持 AND OR,以及 () 的嵌套
     44 def  p_expr4 (p):
     45   '''
     46   expr :
     47    '''
     48
     49 def  p_expr3 (p):
     50   '''
     51   expr : LPAREN expr RPAREN
     52    '''
     53   p[0] = p[2]
     54
     55 def  p_expr2 (p):
     56   '''
     57   expr : condi
     58    '''
     59   p[0] = p[1]
     60
     61 def  p_expr (p):
     62   '''
     63   expr : expr logic_op expr
     64    '''
     65   p[0] = (p[2], p[1], p[3])
     66
     67 def  p_logic_op (p):
     68   '''
     69   logic_op : AND
     70   logic_op : OR
     71    '''
     72   p[0] = p[1]
     73
     74 def  p_condi (p):
     75   '''condi : FIELD cmp_op NUMBER '''
     76   p[0] = (p[2], p[1], p[3])
     77
     78 # for i in p:print i
     79
     80 def  p_cmp_op (p):
     81   '''
     82   cmp_op : GT
     83   cmp_op : EQ
     84   cmp_op : LT
     85    '''
     86   p[0] = p[1]
     87   #for i in p:print i
     88
     89 def  p_error (p):
     90         raise  TypeError("unknow text at %s "%p.value)
     91
     92 yacc.yacc()
     93
     94 #############################################
     95 #                util
     96 #############################################
     97
     98 # 以比较漂亮的形式打印语法树
     99 INDENT = '   '
    100 def  print_tree (t, indent):
    101   if  not  t:
    102     print  indent + str(t)
    103     return
    104   op, l, r = t
    105   if  op in  ('and ', 'or '):
    106     print  indent + op
    107     print_tree(l, indent + INDENT)
    108     print_tree(r, indent + INDENT)
    109   else :
    110     print  indent + str(t)
    111
    112 #############################################
    113 #                judge
    114 #############################################
    115 OP = ('> ', '< ', '== ')
    116 AND = 'and '
    117 OR = 'or '
    118
    119 # 对 age < 18 和 age < 10 这种原子条件表达式对行判断
    120 # 首先要求字段名要相同,否则返回 false
    121 # 然后是根据表达式的比较运算符进行分情况比较
    122 def  is_subset_atom_atom (left, right):
    123   assert  left and  right
    124   lop, ll, lr = left
    125   rop, rl, rr = right
    126   assert  lop in  OP and  rop in  OP
    127   if  ll != rl:
    128     return  False
    129   if  lop == rop == '> ':
    130     return  lr >= rr
    131   elif  lop == rop == '< ':
    132     return  lr <= rr
    133   elif  lop == rop == '== ':
    134     return  lr == rr
    135   elif  lop == '> ':
    136     return  False
    137   elif  lop == '< ':
    138     return  False
    139   elif  lop == '== ':
    140     if  rop == '> ':
    141       return  lr > rr
    142     else : # '<'
    143       return  lr < rr
    144   else :
    145     raise  RuntimeError('Error ')
    146
    147 # 比较 age < 10 和 age < 10 and sex == 0 这种形式的复合表达式
    148 # 这种情况下要求左式必须是右式的两个子式的子集才返回为 true
    149 def  is_subset_atom_and (left, right):
    150   assert  left and  right
    151   rop, rl, rr = right
    152   global  is_subset
    153   return  is_subset(left, rl) and  is_subset(left, rr)
    154
    155 # 比较 age < 10 和 age < 10 or sex == 0 这种形式的复杂表达式
    156 # 这种情况下只需要左式是右式中的任一子式的子集即返回 true
    157 def  is_subset_atom_or (left, right):
    158   assert  left and  right
    159   rop, rl, rr = right
    160   global  is_subset
    161   return  is_subset(left, rl) or  is_subset(left, rr)
    162
    163 # 比较 age < 10 and sex == 0 和 age < 10 这种复合表达式
    164 # 要求左式的任一子式为右式的子集即返回 true
    165 def  is_subset_and_atom (left, right):
    166   assert  left and  right
    167   lop, ll, lr = left
    168   global  is_subset
    169   return  is_subset(ll, right) or  is_subset(lr, right)
    170
    171 # 比较 age < 10 and sex == 0 和 age < 18 and sex == 1 这种复合表达式
    172 # 要求左式的任一子式必须都是右式的*某一*子式的子集才返回 true
    173 def  is_subset_and_and (left, right):
    174   assert  left and  right
    175   lop, ll, lr = left
    176   rop, rl, rr = right
    177   global  is_subset
    178   lresult = is_subset(ll, rl) or  is_subset(ll, rr)
    179   rresult = is_subset(lr, rl) or  is_subset(lr, rr)
    180   return  lresult and  rresult
    181
    182 # 比较 age < 10 and sex == 0 和 age < 10 or sex == 1 这种复合表达式
    183 # 要求整个左式必须是右式的某一子式为真才返回 true
    184 def  is_subset_and_or (left, right):
    185   assert  left and  right
    186   lop, ll, lr = left
    187   rop, rl, rr = right
    188   global  is_subset
    189   return  is_subset(left, rl) or  is_subset(left, rr)
    190
    191 # 比较 age < 10 or sex == 0 和 age < 10 这种复合表达式
    192 # 要求左式的任一子式都必须是右式的子集才返回 true
    193 def  is_subset_or_atom (left, right):
    194   assert  left and  right
    195   lop, ll, lr = left
    196   global  is_subset
    197   return  is_subset(ll, right) and  is_subset(lr, right)
    198
    199 # 比较 age < 10 or sex == 0 和 age < 10 and sex == 0 这种复合表达式
    200 # 与 is_subset_or_atom 是一样的。
    201 def  is_subset_or_and (left, right):
    202   return  is_subset_or_atom(left, right)
    203
    204 # 比较 age < 10 or sex == 0 和 age < 10 or sex == 0 这种复合表达式
    205 # 与 is_subset_or_atom 是一样的。
    206 def  is_subset_or_or (left, right):
    207   return  is_subset_or_atom(left, right)
    208
    209 # 根据左右式的操作符分派给上述的判断函数
    210 def  is_subset (left, right):
    211   assert  left and  right
    212   print  'left: ',left
    213   print  'right: ',right
    214   lop, ll, lr = left
    215   rop, rl, rr = right
    216   if  lop in  OP:
    217     if  rop in  OP:
    218       return  is_subset_atom_atom(left, right)
    219     elif  rop == AND:
    220       return  is_subset_atom_and(left, right)
    221     elif  rop == OR:
    222       return  is_subset_atom_or(left, right)
    223     raise  RuntimeError('Error ')
    224   elif  lop == 'and ':
    225     if  rop in  OP:
    226       return  is_subset_and_atom(left, right)
    227     elif  rop == AND:
    228       return  is_subset_and_and(left, right)
    229     elif  rop == OR:
    230       return  is_subset_and_or(left, right)
    231     else :
    232       raise  RuntimeError('Error ')
    233   elif  lop == 'or ':
    234     if  rop in  OP:
    235       return  is_subset_or_atom(left, right)
    236     elif  rop == AND:
    237       return  is_subset_or_and(left, right)
    238     elif  rop == OR:
    239       return  is_subset_or_or(left, right)
    240     else :
    241       raise  RuntimeError('Error ')
    242   else :
    243     raise  RuntimeError('Error ')
    244
    245 #############################################
    246 #                query
    247 #############################################
    248 class  Query (object):
    249   def  __init__ (self, q):
    250     self._q = q
    251     self._tree = yacc.parse(q)
    252     assert  self._tree
    253     
    254   def  is_subset (self, other):
    255     if  not  self._tree:
    256       return  False
    257     if  not  other._tree:
    258       return  True
    259     return  is_subset(self._tree, other._tree)
    260
    261 #############################################
    262 #                test
    263 #############################################
    264 if  __name__ == '__main__ ':
    265   t0 = Query('age > 40 ') # 中年人
    266   t1 = Query('age > 18 ') # 成年人
    267   print  t0.is_subset(t0)
    268   print  t0.is_subset(t1)
    269   print  t1.is_subset(t0)
    270   print  t1.is_subset(t1)
    271   print  '- '*30
    272   
    273   t2 = Query('age > 18 and weight < 100 ') # 成年瘦子
    274   t3 = Query('age > 18 or weight < 100 ') # 成年人,或体重小于 100
    275   print  t0.is_subset(t2)
    276   print  t0.is_subset(t3)
    277
    278   print  t2.is_subset(t0)
    279   print  t2.is_subset(t3)
    280   
    281   print  t3.is_subset(t2)
    282
    283   r0 = Query('age > 30 and sex == 0 ')
    284   r1 = Query('age > 40 and sex == 0 ')
    285
    286   print  r0.is_subset(r1)
    287   print  r1.is_subset(r0)
    288   print  '= '*30
    289   
    290   t0 = Query('(age < 15 and sex == 0) or age > 30 ')
    291   t1 = Query('age < 7 ')
    292   t2 = Query('age < 18 ')
    293   print_tree(t0._tree, '')
    294   print  '* '*30
    295   assert  't0 is subset of t0:and  t0.is_subset(t0) == True
    296   print  '- '*30
    297   assert  't0 is subset of t1:and  t0.is_subset(t1) == False
    298   print  '- '*30
    299   assert  't1 is subset of t0:and  t1.is_subset(t0) == False
    300   print  '- '*30
    301   assert  't2 is subset of t0:and  t2.is_subset(t0) == False
    302   print  '- '*30
    303   
    304   q0 = Query('age < 15 ')
    305   q1 = Query('age > 30 ')
    306   q2 = Query('age > 18 ')
    307   q3 = Query('age > 40 ')
    308   q4 = Query('age > 30 and sex == 0 ')
    309   
    310
    311   assert  'q0 is subset of q0:and  q0.is_subset(q0) == True
    312   print  '- '*30
    313   assert  'q0 is subset of q1:and  q0.is_subset(q1) == False
    314   print  '- '*30
    315   assert  'q0 is subset of q2:and  q0.is_subset(q2) == False
    316   print  '- '*30
    317   assert  'q0 is subset of q3:and  q0.is_subset(q3) == False
    318   print  '- '*30
    319   assert  'q0 is subset of q4:and  q0.is_subset(q4) == False
    320   print  '- '*30
    321   print
    322
    323   assert  'q1 is subset of q0:and  q1.is_subset(q0) == False
    324   print  '- '*30
    325   assert  'q1 is subset of q1:and  q1.is_subset(q1) == True
    326   print  '- '*30
    327   assert  'q1 is subset of q2:and  q1.is_subset(q2) == True
    328   print  '- '*30
    329   assert  'q1 is subset of q3:and  q1.is_subset(q3) == False
    330   print  '- '*30
    331   assert  'q1 is subset of q4:and  q1.is_subset(q4) == False
    332   print  '- '*30
    333   print
    334
    335   assert  'q2 is subset of q0:and  q2.is_subset(q0) == False
    336   print  '- '*30
    337   assert  'q2 is subset of q1:and  q2.is_subset(q1) == False
    338   print  '- '*30
    339   assert  'q2 is subset of q2:and  q2.is_subset(q2) == True
    340   print  '- '*30
    341   assert  'q2 is subset of q3:and  q2.is_subset(q3) == False
    342   print  '- '*30
    343   assert  'q2 is subset of q4:and  q2.is_subset(q4) == False
    344   print  '- '*30
    345   print
    346
    347   assert  'q3 is subset of q0:and  q3.is_subset(q0) == False
    348   print  '- '*30
    349   assert  'q3 is subset of q1:and  q3.is_subset(q1) == True
    350   print  '- '*30
    351   assert  'q3 is subset of q2:and  q3.is_subset(q2) == True
    352   print  '- '*30
    353   assert  'q3 is subset of q3:and  q3.is_subset(q3) == True
    354   print  '- '*30
    355   assert  'q3 is subset of q4:and  q3.is_subset(q4) == False
    356   print  '- '*30
    357   print
    358
    359   assert  'q4 is subset of q0:and  q4.is_subset(q0) == False
    360   print  '- '*30
    361   assert  'q4 is subset of q1:and  q4.is_subset(q1) == True
    362   print  '- '*30
    363   assert  'q4 is subset of q2:and  q4.is_subset(q2) == True
    364   print  '- '*30
    365   assert  'q4 is subset of q3:and  q4.is_subset(q3) == False
    366   print  '- '*30
    367   assert  'q4 is subset of q4:and  q4.is_subset(q4) == True
    368   print  '- '*30
    369
    370   

  • 相关阅读:
    python3全栈开发-并发编程的多进程理论
    python3全栈开发-补充UDP的套接字、操作系统、并发的理论基础
    python3全栈开发-什么是粘包、粘包现象、如何解决粘包
    python3全栈开发-socket编程
    python3全栈开发- 元类metaclass(面试必考题)
    浏览器窗口
    SQL 笔记
    数据库连接字符串
    获取网卡地址信息
    启动所选择的应用程序
  • 原文地址:https://www.cnblogs.com/aiwz/p/6154366.html
Copyright © 2020-2023  润新知