生成具有最接近请求的结果值的等式,具有速度问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了生成具有最接近请求的结果值的等式,具有速度问题相关的知识,希望对你有一定的参考价值。
我正在写一些问答游戏,如果玩家未能解决问题,需要计算机在测验中解决1个游戏。
鉴于数据:
- 要使用的6个号码的列表,例如4,8,6,2,15,50。
- 目标值,其中0 <值<1000,例如590。
- 可用的操作是除法,加法,乘法和除法。
- 可以使用括号。
生成数学表达式,其中评估与目标值相等或尽可能接近。例如,对于上面给出的数字,表达式可以是:(6 + 4)* 50 + 15 *(8-2)= 590
我的算法如下:
- 从上面的(1)生成给定数字的所有子集的所有排列
- 对于每个排列,生成所有括号和运算符组合
- 算法运行时跟踪最接近的值
我想不出上面的蛮力算法的任何智能优化,这将使其加速数量级。此外,我必须优化最坏的情况,因为许多测验游戏将在服务器上同时运行。
今天为解决这个问题而编写的代码是(从项目中提取的相关内容):
from operator import add, sub, mul, div
import itertools
ops = ['+', '-', '/', '*']
op_map = {'+': add, '-': sub, '/': div, '*': mul}
# iterate over 1 permutation and generates parentheses and operator combinations
def iter_combinations(seq):
if len(seq) == 1:
yield seq[0], str(seq[0])
else:
for i in range(len(seq)):
left, right = seq[:i], seq[i:] # split input list at i`th place
# generate cartesian product
for l, l_str in iter_combinations(left):
for r, r_str in iter_combinations(right):
for op in ops:
if op_map[op] is div and r == 0: # cant divide by zero
continue
else:
yield op_map[op](float(l), r),
('(' + l_str + op + r_str + ')')
numbers = [4, 8, 6, 2, 15, 50]
target = best_value = 590
best_item = None
for i in range(len(numbers)):
for current in itertools.permutations(numbers, i+1): # generate perms
for value, item in iter_combinations(list(current)):
if value < 0:
continue
if abs(target - value) < best_value:
best_value = abs(target - value)
best_item = item
print best_item
它打印:((((4 * 6)+50)* 8)-2)。用不同的值测试了一下它似乎工作正常。此外,我还有一个删除不必要的括号的功能,但它与问题无关,因此不会发布。
问题在于,由于所有这些排列,组合和评估,这种运行速度非常慢。在我的Mac书籍上,它可以运行几分钟,例如。我想让它在同一台机器上运行几秒钟,因为许多测验游戏实例将在服务器上同时运行。所以问题是:
- 我可以以某种方式(按数量级)加速当前算法吗?
- 我是否遗漏了其他算法来解决这个问题,这个算法会运行得更快?
您可以使用给定的数字构建所有可能的表达式树并对其进行评估。您不需要将它们全部保存在内存中,只需在找到目标号码时打印它们:
首先,我们需要一个类来保存表达式。最好将它设计为不可变的,因此它的值可以预先计算。像这样的东西:
class Expr:
'''An Expr can be built with two different calls:
-Expr(number) to build a literal expression
-Expr(a, op, b) to build a complex expression.
There a and b will be of type Expr,
and op will be one of ('+','-', '*', '/').
'''
def __init__(self, *args):
if len(args) == 1:
self.left = self.right = self.op = None
self.value = args[0]
else:
self.left = args[0]
self.right = args[2]
self.op = args[1]
if self.op == '+':
self.value = self.left.value + self.right.value
elif self.op == '-':
self.value = self.left.value - self.right.value
elif self.op == '*':
self.value = self.left.value * self.right.value
elif self.op == '/':
self.value = self.left.value // self.right.value
def __str__(self):
'''It can be done smarter not to print redundant parentheses,
but that is out of the scope of this problem.
'''
if self.op:
return "({0}{1}{2})".format(self.left, self.op, self.right)
else:
return "{0}".format(self.value)
现在我们可以编写一个递归函数,用一组给定的表达式构建所有可能的表达式树,并打印出等于我们目标值的表达式。我们将使用itertools
模块,这总是很有趣。
我们可以使用itertools.combinations()
或itertools.permutations()
,区别在于顺序。我们的一些操作是可交换的,有些则不是,因此我们可以使用permutations()
并假设我们将获得许多非常类似的解决方案。或者我们可以使用combinations()
并在操作不可交换时手动重新排序值。
import itertools
OPS = ('+', '-', '*', '/')
def SearchTrees(current, target):
''' current is the current set of expressions.
target is the target number.
'''
for a,b in itertools.combinations(current, 2):
current.remove(a)
current.remove(b)
for o in OPS:
# This checks whether this operation is commutative
if o == '-' or o == '/':
conmut = ((a,b), (b,a))
else:
conmut = ((a,b),)
for aa, bb in conmut:
# You do not specify what to do with the division.
# I'm assuming that only integer divisions are allowed.
if o == '/' and (bb.value == 0 or aa.value % bb.value != 0):
continue
e = Expr(aa, o, bb)
# If a solution is found, print it
if e.value == target:
print(e.value, '=', e)
current.add(e)
# Recursive call!
SearchTrees(current, target)
# Do not forget to leave the set as it were before
current.remove(e)
# Ditto
current.add(b)
current.add(a)
然后是主要电话:
NUMBERS = [4, 8, 6, 2, 15, 50]
TARGET = 590
initial = set(map(Expr, NUMBERS))
SearchTrees(initial, TARGET)
并做了!有了这些数据,我将在21秒内获得719种不同的解决方案!当然,其中许多是同一表达的微不足道的变体。
我会尝试至少使用AST 使表达式生成部分更容易 (不需要乱用括号)。 http://en.wikipedia.org/wiki/Abstract_syntax_tree 1)生成具有N个节点的一些树 (N =您拥有的数字)。 我之前读过你们中有多少人 随着N的增长,它们的大小是严重的。 严肃地说,我的意思不仅仅是多项式,至少可以说。 2)现在开始改变操作 在非叶节点中并继续评估 结果。 但这又是回溯和过多的自由度。 这是一项计算复杂的任务。我相信如果你 像你一样问问题:“让我们在输出上生成一个数字K. 这样| K-V |是最小的“(这里V是预定义的期望结果, 即你的例子中的590),那么我猜这个问题甚至是NP完全的。 如果我的直觉对我撒谎,请有人纠正我。 所以我认为甚至生成所有可能的AST(假设只有1次操作 允许)是NP完整的,因为它们的计数不是多项式的。不要再说了 这里允许超过1次操作,而不是谈到最小差异要求 (在结果和期望的结果之间)。
24个游戏是4个数字到目标24,你的游戏是6个数字到目标x(0 <x <1000)。
那非常相似。
这是快速解决方案,获取所有结果并在我的rMBP中打印一个关于1-3s
,我认为在这个游戏中一个解决方案打印是好的:),我将在稍后解释:
def mrange(mask):
#twice faster from Evgeny Kluev
x = 0
while x != mask:
x = (x - mask) & mask
yield x
def f( i ) :
global s
if s[i] :
#get cached group
return s[i]
for x in mrange(i & (i - 1)) :
#when x & i == x
#x is a child group in group i
#i-x is also a child group in group i
fk = fork( f(x), f(i-x) )
s[i] = merge( s[i], fk )
return s[i]
def merge( s1, s2 ) :
if not s1 :
return s2
if not s2 :
return s1
for i in s2 :
#print just one way quickly
s1[i] = s2[i]
#combine all ways, slowly
# if i in s1 :
# s1[i].update(s2[i])
# else :
# s1[i] = s2[i]
return s1
def fork( s1, s2 ) :
d = {}
#fork s1 s2
for i in s1 :
for j in s2 :
if not i + j in d :
d[i + j] = getExp( s1[i], s2[j], "+" )
if not i - j in d :
d[i - j] = getExp( s1[i], s2[j], "-" )
if not j - i in d :
d[j - i] = getExp( s2[j], s1[i], "-" )
if not i * j in d :
d[i * j] = getExp( s1[i], s2[j], "*" )
if j != 0 and not i / j in d :
d[i / j] = getExp( s1[i], s2[j], "/" )
if i != 0 and not j / i in d :
d[j / i] = getExp( s2[j], s1[i], "/" )
return d
def getExp( s1, s2, op ) :
exp = {}
for i in s1 :
for j in s2 :
exp['('+i+op+j+')'] = 1
#just print one way
break
#just print one way
break
return exp
def check( s ) :
num = 0
for i in xrange(target,0,-1):
if i in s :
if i == target :
print numbers, target, "
Find ", len(s[i]), 'ways'
for exp in s[i]:
print exp, ' = ', i
else :
print numbers, target, "
Find nearest ", i, 'in', len(s[i]), 'ways'
for exp in s[i]:
print exp, ' = ', i
break
print '
'
def game( numbers, target ) :
global s
s = [None]*(2**len(numbers))
for i in xrange(0,len(numbers)) :
numbers[i] = float(numbers[i])
n = len(numbers)
for i in xrange(0,n) :
s[2**i] = { numbers[i]: {str(numbers[i]):1} }
for i in xrange(1,2**n) :
#we will get the f(numbers) in s[2**n-1]
s[i] = f(i)
check(s[2**n-1])
numbers = [4, 8, 6, 2, 2, 5]
s = [None]*(2**len(numbers))
target = 590
game( numbers, target )
numbers = [1,2,3,4,5,6]
target = 590
game( numbers, target )
假设A
是你的6个数字列表。
我们定义f(A)
是可以通过所有A
数计算的所有结果,如果我们搜索f(A)
,我们将找到目标是否在其中并获得答案或最接近的答案。
我们可以将A
分成两个真正的子组:A1
和A-A1
(A1不是空的而不等于A),它将问题从f(A)
切换到f(A1)
和f(A-A1)
。因为我们知道f(A) = Union( a+b, a-b, b-a, a*b, a/b(b!=0), b/a(a!=0) )
,A
中的a,A-A1
中的b。
我们使用fork f(A) = Union( fork(A1,A-A1) )
代表这样的过程。我们可以删除fork()
中的所有重复值,因此我们可以缩小范围并使程序更快。
所以,如果A = [1,2,3,4,5,6]
,那么f(A) = fork( f([1]),f([2,3,4,5,6]) ) U ... U fork( f([1,2,3]), f([4,5,6]) ) U ...
U
代表联盟。
我们将看到f([2,3,4,5,6])= fork(f([2,3]),f([4,5,6]))U ...,f([3, 4,5,6])= fork(f([3]),f([4,5,6]))U ...,两者中使用的f([4,5,6])。
因此,如果我们可以缓存每个f([...]),程序可以更快。
我们可以在A中得到2^len(A) - 2
(A1,A-A1)。我们可以使用二进制代表它。
例如:A = [1,2,3,4,5,6],A1 = [1,2,3],则二元000111
(7)代表A1。 A2 = [1,3,5],二元010101
(21)代表A2。 A3 = [1],然后二进制000001
(1)代表A3 ......
所以我们得到一个代表A中所有组的方法,我们可以缓存它们并使所有进程更快!
六个数字,四个操作和括号的所有组合最多为5 * 9!至少。所以我认为你应该使用一些AI算法。使用遗传编程或优化似乎是要遵循的道路。
在第11章“进化智力”一书中的Extjs 组合框显示具有从数据存储 rest/jsp 请求中获取的值的记录