如何优化查找表达式中缺失的算术运算?

问题描述 投票:0回答:1

我被赋予以下任务:

理查德喜欢问他的同学以下问题:

4×4×3=13

为了解决这个任务,他的同学必须填写缺失的算术运算符(+、-、*、/),以确保所创建的方程为真。对于此示例,正确的解决方案是 4 * 4 - 3。编写一段代码来创建与上述问题类似的问题。应符合以下规则:

  1. 谜语应该只有一种解决方案(例如,不是 3 x 4 x 3 = 15,它有两种解决方案)
  2. 操作数是1-9之间的数字
  3. 解是正整数
  4. 考虑破折号之前的点(例如 1 + 3 * 4 = 13)> 5)每个中间结果都是整数(例如不是 3 / 2 * 4 = 6)

创建最多 15 个算术操作数的谜语,并遵循以下规则。

我的进步:

import random
import time
import sys


add = lambda a, b: a + b
sub = lambda a, b: a - b
mul = lambda a, b: a * b
div = lambda a, b: a / b if a % b == 0 else 0 / 0

operations = [(add, '+'),
              (sub, '-'),
              (mul, '*'),
              (div, '/')]

operations_mul = [(mul, '*'),
                  (add, '+'),
                  (sub, '-'),
                  (div, '/')]

res = []


def ReprStack(stack):
    reps = [str(item) if type(item) is int else item[1] for item in stack]
    return ''.join(reps)


def Solve(target, numbers):
    counter = 0

    def Recurse(stack, nums):
        global res
        nonlocal counter
        valid = True
        end = False
        for n in range(len(nums)):
            stack.append(nums[n])
            remaining = nums[:n] + nums[n + 1:]
            pos = [position for position, char in enumerate(stack) if char == operations[3]]
            for j in pos:
                if stack[j - 1] % stack[j + 1] != 0:
                    valid = False

            if valid:
                if len(remaining) == 0:
                    # Überprüfung, ob Stack == target
                    solution_string = str(ReprStack(stack))
                    if eval(solution_string) == target:
                        if not check_float_division(solution_string)[0]:
                            counter += 1
                            res.append(solution_string)
                            if counter > 1:
                                res = []
                                values(number_ops)
                                target_new, numbers_list_new = values(number_ops)
                                Solve(target_new, numbers_list_new)
                else:
                    for op in operations_mul:
                        stack.append(op)
                        stack = Recurse(stack, remaining)
                        stack = stack[:-1]
            else:
                if len(pos) * 2 + 1 == len(stack):
                    end = True
                if counter == 1 and end:
                    print(print_solution(target))
                    sys.exit()
            stack = stack[:-1]
            return stack

    Recurse([], numbers)


def simplify_multiplication(solution_string):
    for i in range(len(solution_string)):
        pos_mul = [position for position, char in enumerate(solution_string) if char == '*']
        if solution_string[i] == '*' and len(pos_mul) > 0:
            ersatz = int(solution_string[i - 1]) * int(solution_string[i + 1])
            solution_string_new = solution_string[:i - 1] + solution_string[i + 1:]
            solution_string_new_list = list(solution_string_new)
            solution_string_new_list[i - 1] = str(ersatz)
            solution_string = ''.join(str(x) for x in solution_string_new_list)
        else:
            return solution_string

    return solution_string


def check_float_division(solution_string):
    pos_div = []
    solution_string = simplify_multiplication(solution_string)
    if len(solution_string) > 0:
        for i in range(len(solution_string)):
            pos_div = [position for position, char in enumerate(solution_string) if char == '/']
            if len(pos_div) == 0:
                return False, pos_div
            for j in pos_div:
                if int(solution_string[j - 1]) % int(solution_string[j + 1]) != 0:
                    # Float division
                    return True, pos_div
            else:
                # No float division
                return False, pos_div


def new_equation(number_ops):
    equation = []
    operators = ['+', '-', '*', '/']
    ops = ""
    if number_ops > 1:
        for i in range(number_ops):
            ops = ''.join(random.choices(operators, weights=(4, 4, 4, 4), k=1))
            const = random.randint(1, 9)
            equation.append(const)
            equation.append(ops)
        del equation[-1]
        pos = check_float_division(equation)[1]
        if check_float_division(equation):
            if len(pos) == 0:
                return equation
            for i in pos:
                equation[i] = ops
        else:
            '''for i in pos:
                if equation[i+1] < equation[i-1]:
                    while equation[i-1] % equation[i+1] != 0:
                        equation[i+1] += 1'''
            new_equation(number_ops)
    else:
        print("No solution with only one operand")
        sys.exit()
    return equation


def values(number_ops):
    target = 0
    equation = ''
    while target < 1:
        equation = ''.join(str(e) for e in new_equation(number_ops))
        target = eval(equation)
    numbers_list = list(
        map(int, equation.replace('+', ' ').replace('-', ' ').replace('*', ' ').replace('/', ' ').split()))
    return target, numbers_list


def print_solution(target):
    equation_encrypted_sol = ''.join(res).replace('+', '○').replace('-', '○').replace('*', '○').replace('/', '○')
    print("Try to find the correct operators " + str(equation_encrypted_sol) + " die Zahl " + str(
        target))
    end_time = time.time()
    print("Duration: ", end_time - start_time)
    input(
        "Press random button and after that "ENTER" in order to generate result")
    print(''.join(res))


if __name__ == '__main__':
    number_ops = int(input("Number of arithmetic operators: "))
    # number_ops = 10
    target, numbers_list = values(number_ops)
    # target = 590
    # numbers_list = [9, 3, 5, 3, 5, 2, 6, 3, 4, 7]
    start_time = time.time()
    Solve(target, numbers_list)

Solve(target, numbers)
使用暴力。它接收一个方程和相应的结果作为输入,该结果是在
new_equation(number_ops)
中生成的。

Solve(target, numbers)
使用堆栈找到解决方案。 15 名操作员完成一项任务大约需要两个小时。如何让它更快?

python algorithm recursion brute-force
1个回答
2
投票

这主要是蛮力,但 15 的运算公式只需要几秒钟。

为了检查结果,我首先创建了一个

solve
函数(递归迭代器)来生成解决方案:

def multiplyDivide(numbers):
    if len(numbers) == 1:     # only one number, output it directly
        yield numbers[0],numbers
        return
    product,n,*numbers = numbers
    if product % n == 0: # can use division.
        for value,ops in multiplyDivide([product//n]+numbers):
            yield value, [product,"/",n] + ops[1:]
    for value,ops in multiplyDivide([product*n]+numbers):
        yield value, [product,"*",n] + ops[1:]    

def solve(target,numbers,canGroup=True): 
    *others,last = numbers
    if not others:             # only one number
        if last == target:     # output it if it matches target
            yield [last]
        return
    yield from ( sol + ["+",last] for sol in solve(target-last,others)) # additions
    yield from ( sol + ["-",last] for sol in solve(target+last,others)) # subtractions
    if not canGroup: return
    for size in range(2,len(numbers)+1):
        for value,ops in multiplyDivide(numbers[-size:]): # multiplicative groups
            for sol in solve(target,numbers[:-size]+[value],canGroup=False):
                yield sol[:-1] + ops  # combined multipicative with rest

solve
函数通过数字进行递归,与最后一个数字进行加法或减法,并递归以解决调整后的目标和少一个数字的较小问题。

除了加法和减法之外,

solve
函数还将数字(从末尾开始)分组为连续的乘法/除法,并使用结果值对其进行处理(递归到
solve
),该结果值的计算优先级高于加法/减法.

multiplyDivide
函数(也是一个递归生成器)将给定的一组数字与从左到右执行的乘法和除法相结合。仅当当前乘积除以附加数产生整数中间结果时才添加除法。

使用

solve
迭代器,我们可以找到第一个解,并通过额外迭代一次来知道是否还有更多解:

def findOper(S):
    expression,target = S.split("=")
    target   = int(target.strip())
    numbers  = [ int(n.strip()) for n in expression.split("x") ]
    iSolve   = solve(target,numbers)
    solution = next(iSolve,["no solution"])
    more     = " and more" * bool(next(iSolve,False))
    return " ".join(map(str,solution+ ["=",target])) + more

输出:

print(findOper("10 x 5 = 2"))
# 10 / 5 = 2

print(findOper("10 x 5 x 3 = 6"))
# 10 / 5 * 3 = 6

print(findOper("4 x 4 x 3 = 13"))
# 4 * 4 - 3 = 13

print(findOper("1 x 3 x 4 = 13"))
# 1 + 3 * 4 = 13

print(findOper("3 x 3 x 4 x 4 = 25"))
# 3 * 3 + 4 * 4 = 25

print(findOper("2 x 6 x 2 x 4 x 4 = 40"))
# 2 * 6 * 2 + 4 * 4 = 40 and more

print(findOper("7 x 6 x 2 x 4 = 10"))
# 7 + 6 * 2 / 4 = 10

print(findOper("2 x 2 x 3 x 4 x 5 x 6 x 3 = 129"))
# 2 * 2 * 3 + 4 * 5 * 6 - 3 = 129

print(findOper("1 x 2 x 3 x 4 x 5 x 6 = 44"))
# 1 * 2 + 3 * 4 + 5 * 6 = 44

print(findOper("1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 8 x 7 x 6 x 5 x 4 x 1= 1001"))
# 1 - 2 - 3 + 4 * 5 * 6 * 7 / 8 * 9 + 8 * 7 - 6 + 5 + 4 + 1 = 1001 and more

print(findOper("1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 8 x 7 x 6 x 5 x 4 x 1= 90101"))
# no solution = 90101  (15 seconds, worst case is not finding any solution)

为了创建单个解决方案谜语,

solve
函数可以转换为结果生成器(
genResult
),它将产生所有可能的计算结果。这将使我们能够找到对于给定的随机数列表仅具有一种操作组合的结果。鉴于所有数字的乘法很可能是唯一的结果,这将快速收敛,而无需经过太多随机列表:

import random

def genResult(numbers,canGroup=True):
    *others,last = numbers
    if not others:
        yield last
        return    
    for result in genResult(others):
        yield result - last
        yield result + last
    if not canGroup: return
    for size in range(2,len(numbers)+1):
        for value,_ in multiplyDivide(numbers[-size:]): 
            yield from genResult(numbers[:-size]+[value],canGroup=False)

def makeRiddle(size=5):
    singleSol = []
    while not singleSol:
        counts  = dict()
        numbers = [random.randint(1,9) for _ in range(size)]
        for final in genResult(numbers):
            if final < 0 : continue
            counts[final] = counts.get(final,0) + 1
        singleSol = [n for n,c in counts.items() if c==1]        
    return " x ".join(map(str,numbers)) + " = " + str(random.choice(singleSol))

我们必须循环

singleSol
(单一解决方案结果)的原因是,在某些情况下,即使所有数字的乘积也不是唯一的解决方案。例如:
1 x 2 x 3 = 6
可以是乘积
1 * 2 * 3 = 6
,但也可以是总和
1 + 2 + 3 = 6
。这种情况并不多,但仍然有可能,因此出现了循环。在使用 5 个算子生成 1000 个谜语的测试中,这种情况发生了 4 次(例如
4 x 1 x 1 x 4 x 2
没有唯一的解决方案)。随着谜语大小的增加,这些非唯一解决方案模式的出现会变得更加频繁(例如,使用 15 个运算符 6 次生成 20 个谜语)。

输出:

S = makeRiddle(15)  # 7 seconds

print(S)

# 1 x 5 x 2 x 5 x 8 x 2 x 3 x 4 x 1 x 2 x 8 x 9 x 7 x 3 x 2 = 9715

print(findOper(S)) # confirms that there is only one solution

# 1 * 5 * 2 * 5 * 8 * 2 * 3 * 4 - 1 + 2 + 8 * 9 + 7 * 3 * 2 = 9715
© www.soinside.com 2019 - 2024. All rights reserved.