百姓网那道题

赖勇浩(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   

posted on 2010-05-06 23:23  哼哼唧唧  阅读(91)  评论(0编辑  收藏  举报

导航