Python 生成所有可能的“;独特的;RPN(反向波兰符号)表达式
我想在Python中生成所有可能的RPN()表达式,这些表达式使用输入列表中的字母(例如Python 生成所有可能的“;独特的;RPN(反向波兰符号)表达式,python,algorithm,recursion,postfix-notation,rpn,Python,Algorithm,Recursion,Postfix Notation,Rpn,我想在Python中生成所有可能的RPN()表达式,这些表达式使用输入列表中的字母(例如['a','b','c']),并包含运算符['+','-','*','/'] 我的想法是,我们可以将元素添加到当前表达式中,直到出现以下情况之一:要么使用了所有字母,要么表达式已完成(即,我们不能添加更多运算符) 因此,我编写了以下函数: (一) (三) 接下来,我将其包装为递归: #exp -- current expression, base of recursion is exp = '' def re
['a','b','c']
),并包含运算符['+','-','*','/']
我的想法是,我们可以将元素添加到当前表达式中,直到出现以下情况之一:要么使用了所有字母,要么表达式已完成(即,我们不能添加更多运算符)
因此,我编写了以下函数:
(一)
(三)
接下来,我将其包装为递归:
#exp -- current expression, base of recursion is exp = ''
def rec(exp, Final_sol = []):
elements_to_try = possible_elements(letters, exp)
for i in elements_to_try:
if len(possible_elements(letters, exp + i)) == 0:
Final_sol.append(exp + i)
else:
rec(exp+i, Final_sol)
return Final_sol
#we start with an empty string
Final_sol = rec('')
print(len(Final_sol)) #7680
该功能有两个困难:
字母=['a','b','a']
:
Final_sol = rec('')
print(len(Final_sol)) #32
str(Final_sol)
>> "['ab+', 'ab-', 'ab*', 'ab/', 'ba+', 'ba-', 'ba*', 'ba/', 'ba+', 'ba-',
'ba*', 'ba/', 'ab+', 'ab-', 'ab*', 'ab/', 'ab+', 'ab-', 'ab*', 'ab/', 'ba+',
'ba-', 'ba*', 'ba/', 'ba+', 'ba-', 'ba*', 'ba/', 'ab+', 'ab-', 'ab*',
'ab/']"
因此,输出缺少“ab+a+”,依此类推。但我真的想把所有的东西都还给你
在这种情况下,可能的组合也是如此ab+c++
/abc++
/ca+b++
应该被视为等效的:我只希望每个组中有一个
函数rec()的输出from itertools import permutations
variables = ['a', 'a', 'b', 'c']
operators = ['+', '-', '*', '/']
equations = set()
for permutation in permutations(variables):
a, b, *rest = permutation
operations = permutations(operators)
for permutation in operations:
equation = zip([a + b, *rest], permutation)
equations.add("".join(variable + operator for variable, operator in equation))
使用set()
将消除由重复变量引起的任何重复
第二个问题是,在
输出。因为我们有交换性和结合性
为了解决交换问题,我们将使用模式匹配来简化方程:
import sys
import re
DEBUG = True
remove = set()
# Reduce commutative equivalents: ca*a-b/ same as ac*a-b/
if DEBUG:
print("Reduce commutative equivalents:", file=sys.stderr)
for equation in equations:
if equation not in remove:
for match in re.finditer(r"(?=(.+)(\w)[+*])", equation):
a, _ = match.span(1)
_, d = match.span(2)
equivalent = equation[:a] + match[2] + match[1] + equation[d:]
if equivalent != equation and equivalent in equations:
remove.add(equivalent)
if DEBUG:
print(f"Removed {equivalent} same as {equation}", file=sys.stderr)
equations -= remove
因为我们已经建立了所有方程,如ab op c op d op,等等。我不相信我们会生成关联等价物,但如果我们生成了,我们可以使用类似的技术来尝试细化它们:
remove = set()
# Reduce associative equivalents aa+b*c- same as ab*ab*+c-
if DEBUG:
print("Reduce associative equivalents:", file=sys.stderr)
for equation in equations:
if equation not in remove:
for match in re.finditer(r"(?=(\w)([+])(\w)([*]))", equation):
a, _ = match.span(1)
_, d = match.span(4)
equivalent = equation[:a] + match[3] + match[4] + match[1] + match[3] + match[4] + match[2] + equation[d:]
if equivalent != equation and equivalent in equations:
remove.add(equivalent)
if DEBUG:
print(f"Removed {equivalent} same as {equation}", file=sys.stderr)
equations -= remove
最后,将我们的缩减集转储:
if DEBUG:
print("Final equations:", file=sys.stderr)
print(equations)
输出
> python3 test.py
Reduce commutative equivalents:
Removed ac+a-b/ same as ca+a-b/
Removed ab*a/c- same as ba*a/c-
Removed cb*a/a- same as bc*a/a-
Removed ac+b-a/ same as ca+b-a/
Removed ba+c/a- same as ab+c/a-
Removed ba+a-c/ same as ab+a-c/
Removed ac+a/b- same as ca+a/b-
Removed ac+b/a- same as ca+b/a-
Removed ac*b-a/ same as ca*b-a/
Removed bc*a-a/ same as cb*a-a/
Removed ca*a-b/ same as ac*a-b/
Removed ba*a-c/ same as ab*a-c/
Removed cb+a/a- same as bc+a/a-
Removed ba+c-a/ same as ab+c-a/
Removed ca*a/b- same as ac*a/b-
Removed ca*b/a- same as ac*b/a-
Removed ba+a/c- same as ab+a/c-
Removed ab*c-a/ same as ba*c-a/
Removed ab*c/a- same as ba*c/a-
Removed cb+a-a/ same as bc+a-a/
Reduce associative equivalents:
Final equations:
{'ca+a-b/', 'cb*a+a-', 'aa/b-c*', 'ba/c-a*', 'cb/a-a*', 'ab+a*c/', 'aa/c+b-',
'bc/a-a+', 'aa*b+c-', 'ba*a/c-', 'ab+c/a*', 'ca-a/b+', 'ca-b+a*', 'bc*a/a-',
'bc/a+a*', 'ac+a/b*', 'bc+a*a-', 'ca/a-b+', 'ac-a*b+', 'ba-a*c/', 'ac/b-a*',
'ba-c+a*', 'ba+a-c*', 'aa+b/c-', 'ca-b*a/', 'ca+b-a/', 'ab+c/a-', 'ac*b+a-',
'aa+c-b/', 'aa*c/b-', 'ab/c*a+', 'ac+b/a*', 'aa+b*c/', 'ab-a*c+', 'ac+a-b*',
'cb-a+a*', 'cb*a/a+', 'ab-c/a+', 'ac*b+a/', 'ba*c/a+', 'ba/c+a*', 'aa-b*c+',
'aa/b+c*', 'ab-c*a+', 'ac+a*b/', 'ac/b+a-', 'aa*b-c+', 'ac-a+b/', 'aa-c*b+',
'ab+a-c/', 'aa-c+b/', 'ba+c*a/', 'ca-b*a+', 'ab-a/c*', 'aa-b/c+', 'ac*a+b/',
'ba/a+c-', 'ba-c/a+', 'cb/a+a*', 'ca+b/a*', 'aa/c*b+', 'ac-a+b*', 'ba-a+c*',
'ca+a*b/', 'aa+b/c*', 'aa/c-b+', 'bc*a/a+', 'ca+a/b-', 'ca+b/a-', 'ca*b-a/',
'ac/b*a-', 'aa*b/c+', 'ba/a*c+', 'bc/a*a+', 'ca-b+a/', 'ac/b+a*', 'aa*b/c-',
'bc-a+a/', 'ca/b-a*', 'ba-c*a/', 'cb*a-a/', 'ba-c/a*', 'aa*b+c/', 'ac*a-b/',
'ca*b/a+', 'aa+b-c*', 'ba/a-c*', 'ca-b/a+', 'ab/c-a+', 'cb+a/a*', 'aa-c/b*',
'ba+c*a-', 'cb*a+a/', 'aa*c/b+', 'ab/c+a*', 'ca+b-a*', 'aa+b-c/', 'ac-b*a/',
'ab*a-c/', 'ba-a*c+', 'ba*c+a-', 'bc/a*a-', 'ba*c-a+', 'ba/c*a+', 'ab-c+a/',
'ba*c+a/', 'ca*a-b+', 'bc+a/a-', 'aa+c*b-', 'ab+c*a-', 'ac-a/b+', 'ca+a-b*',
'aa+c-b*', 'ab/c*a-', 'ab+c-a/', 'bc+a/a*', 'ac-a/b*', 'ab/a-c*', 'ac/a-b+',
'bc-a/a+', 'ab+a*c-', 'ac/a-b*', 'ca*a+b-', 'ab/a-c+', 'ab-a*c/', 'cb/a*a-',
'ac/a+b*', 'bc-a/a*', 'ac-b+a*', 'ac*a/b-', 'ba*a+c-', 'ba/a-c+', 'bc/a+a-',
'aa/b-c+', 'cb+a-a*', 'ca-b/a*', 'ca+b*a-', 'ac*b/a-', 'ca-a+b/', 'ca/b*a-',
'ba+a/c*', 'cb-a*a+', 'ac+a*b-', 'aa*b-c/', 'aa*c-b/', 'ac/a*b+', 'aa-c+b*',
'ca*a+b/', 'ca/b+a-', 'ac*a/b+', 'aa+c/b-', 'ab/c+a-', 'ab+a/c-', 'cb-a+a/',
'ab*a-c+', 'ab-a+c*', 'ab+a/c*', 'ac/b-a+', 'ab*c+a/', 'ba/c+a-', 'ba/c*a-',
'cb-a*a/', 'ac+b*a-', 'ba+c-a*', 'ac/b*a+', 'cb/a*a+', 'cb-a/a+', 'bc*a+a/',
'ac*b/a+', 'cb+a*a-', 'ba*c-a/', 'ca-a*b/', 'ca-a*b+', 'ab/a*c-', 'ba-a+c/',
'ba*a/c+', 'bc-a+a*', 'ca+a/b*', 'ca*a/b+', 'aa*c+b-', 'ba*c/a-', 'bc/a-a*',
'ca/a+b*', 'ab-a+c/', 'ca/b*a+', 'ab-a/c+', 'cb*a-a+', 'aa-b/c*', 'ac-b/a+',
'aa*c-b+', 'ab*c+a-', 'cb/a-a+', 'ab/a+c*', 'ba+a*c-', 'ba*a+c/', 'ba-a/c*',
'aa/b+c-', 'ba/c-a+', 'ca/b-a+', 'ab*a/c+', 'bc+a-a*', 'bc*a-a+', 'ab+c*a/',
'ab-c*a/', 'ac*a+b-', 'ca/a+b-', 'ac/a*b-', 'ac+b-a*', 'ba/a+c*', 'ba-a/c+',
'ab*c/a+', 'cb/a+a-', 'ca/a-b*', 'ac-b/a*', 'ab/a*c+', 'ca*b+a/', 'ac-a*b/',
'aa/b*c+', 'aa/c-b*', 'ca/a*b+', 'bc-a*a/', 'ca+b*a/', 'aa*c+b/', 'ab*a+c/',
'bc+a*a/', 'ab-c/a*', 'ca-a+b*', 'aa-c*b/', 'cb-a/a*', 'aa+b*c-', 'ca+a*b-',
'aa-b+c*', 'ac/a+b-', 'ba-c+a/', 'ba-c*a+', 'ca*b-a+', 'ac-b+a/', 'aa-b*c/',
'aa-b+c/', 'ac*a-b+', 'ac+b*a/', 'ca/a*b-', 'bc+a-a/', 'bc-a*a+', 'ba+a*c/',
'ac*b-a+', 'aa/c+b*', 'ab/a+c-', 'ab/c-a*', 'ab-c+a*', 'ba+c/a*', 'ab*c-a+',
'ab+a-c*', 'cb+a*a/', 'ac-b*a+', 'ba/a*c-', 'ab*a+c-', 'ab+c-a*', 'bc*a+a-',
'aa/b*c-', 'ca*b+a-', 'ba*a-c+', 'ca/b+a*', 'aa-c/b+', 'aa+c/b*', 'ca-a/b*',
'aa/c*b-', 'aa+c*b/'}
>
我并没有要求一个完美的解决方案,只是说明了一些可用的工具来解决你的问题。
< p>创建所有可能的表达式,我们可以考虑每一个表达式A,然后符号只是一个问题,遍历不同的树。例如:tree: *
/ \
+ - c
/ \ / \
a b a b
infix: a + b (a - b) * c
postfix a b + a b - c *
由于所有必需的运算符都是二进制的,因此生成的表达式树是完整的二叉树,这意味着所有非叶节点正好有两个子节点。二元表达式树的另一个特性是,所有操作数都是树的叶子,所有内部节点都是运算符,并且内部节点(运算符)的数量比叶子(操作数)的数量少一个
现在要创建所有可能的表达式,首先我们需要所有结构不同的完整二叉树,它们具有len(操作数)
leaves或len(操作数)-1
内部节点
我使用的生成器由回答此问题的人编写:
下面的代码生成具有n
叶子的所有结构不同的全二叉树。它输出带有一些符号的树结构,您可以在函数中设置这些符号。此选项设置为显示用括号括起来的子树,操作数显示为x
,运算符显示为o
。例如,对于2个运算符和3个操作数:
(xo(xox)) ((xox)ox)
o o
/ \ / \
x o o x
/ \ / \
x x x x
(n+1)! x 4^n x (1/n+1) x (2n)! / (n! x n!) = 4^n x (2n)! / n!
现在,为了生成所有可能的表达式,我们需要将所有不重复操作数的排列放在叶子上,并将所有长度len(操作数)-1
的重复运算符排列放在每个树结构的内部节点上。在这里,我们修改生成器函数以使用运算符和操作数列表以及输出后缀表达式:
from itertools import permutations, product
def expressions(opds, oprs, idx):
if len(opds) == 1:
yield opds[0]
for i in range(1, len(opds)):
left = expressions(opds[0:i], oprs, idx+1)
right = expressions(opds[i:], oprs, idx+1)
for l, r in product(left, right):
yield l+r+oprs[idx]
operands = ['a', 'b', 'c']
operators = ['+', '-', '*', '/']
operatorProducts = product(operators, repeat=len(operands)-1)
operandPermutations = permutations(operands)
for opds, oprs in product(operandPermutations, operatorProducts):
for t in expressions(opds, oprs, 0):
print(t)
现在谈谈时间复杂性。作为示例,让我们计算['a','b','c']
的所有结构不同的表达式的数量
正如我们前面看到的,三个操作数有两个完整的二叉树。操作数的排列数为3!=6
运算符的排列数为4^2
,因为我们从4个运算符中选择2个,并且允许重复。因此,我们有:
number of expressions
= number of trees * number of operand permutations * number of operator permutations
= 2 * 6 * 16
= 192
对于一般公式,有趣的部分是结构不同的二叉树的数量,即n,n是树的内部节点的数量。您可以在的答案中阅读更多关于它的信息
因此,具有n
运算符或n+1
操作数的结构不同的表达式的数量:
(xo(xox)) ((xox)ox)
o o
/ \ / \
x o o x
/ \ / \
x x x x
(n+1)! x 4^n x (1/n+1) x (2n)! / (n! x n!) = 4^n x (2n)! / n!
(请原谅这里缺少支持的丑陋数学公式。x
是乘法。您可以在上面的链接中找到更好的格式。)
请注意,n
是数字运算符或操作数-1
正如您所见,随着n
的增加,可能的表达式数量增长得非常快
1, 8, 192, 7680, 430080, 30965760, ...
虽然有许多等价表达式,但它们仍然是所有表达式的一小部分,您应该考虑操作数的实际限制
这就引出了下一个问题,那就是寻找等价的表达式。一开始它可能看起来很简单,因为人们可能认为它只是关于+
和*
的交换属性,但也有-
和/
以复杂的方式更改表达式的其余部分的情况,这仅仅是一个简单的RegExp很难捕捉到的,例如,abc--
相当于ab-c+
,因为负号对括号中的元素有一元效应,而更复杂的版本有除法的反转效应,abcde+-*/
相当于abo-e-/
。将重复元素添加到操作数列表中会创建更多等效表达式并使其
(n+1)! x 4^n x (1/n+1) x (2n)! / (n! x n!) = 4^n x (2n)! / n!
1, 8, 192, 7680, 430080, 30965760, ...