生成具有最接近请求的结果值的等式,具有速度问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了生成具有最接近请求的结果值的等式,具有速度问题相关的知识,希望对你有一定的参考价值。

我正在写一些问答游戏,如果玩家未能解决问题,需要计算机在测验中解决1个游戏。

鉴于数据:

  1. 要使用的6个号码的列表,例如4,8,6,2,15,50。
  2. 目标值,其中0 <值<1000,例如590。
  3. 可用的操作是除法,加法,乘法和除法。
  4. 可以使用括号。

生成数学表达式,其中评估与目标值相等或尽可能接近。例如,对于上面给出的数字,表达式可以是:(6 + 4)* 50 + 15 *(8-2)= 590

我的算法如下:

  1. 从上面的(1)生成给定数字的所有子集的所有排列
  2. 对于每个排列,生成所有括号和运算符组合
  3. 算法运行时跟踪最接近的值

我想不出上面的蛮力算法的任何智能优化,这将使其加速数量级。此外,我必须优化最坏的情况,因为许多测验游戏将在服务器上同时运行。

今天为解决这个问题而编写的代码是(从项目中提取的相关内容):

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书籍上,它可以运行几分钟,例如。我想让它在同一台机器上运行几秒钟,因为许多测验游戏实例将在服务器上同时运行。所以问题是:

  1. 我可以以某种方式(按数量级)加速当前算法吗?
  2. 我是否遗漏了其他算法来解决这个问题,这个算法会运行得更快?
答案

您可以使用给定的数字构建所有可能的表达式树并对其进行评估。您不需要将它们全部保存在内存中,只需在找到目标号码时打印它们:

首先,我们需要一个类来保存表达式。最好将它设计为不可变的,因此它的值可以预先计算。像这样的东西:

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分成两个真正的子组:A1A-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 请求中获取的值的记录

Python列表查找具有接近值的元素

如何使用具有超过 2^31 个观测值的 biglm

最接近某个值的元素(逐元素,numpy 数组)

具有大量局部最小值的多参数优化

获取当前邮政编码,并显示来自 NSArray 的最接近的结果