推导集合中所有可能的数学表达式以获得目标值的算法

时间:2018-07-03 17:08:20

标签: swift algorithm

我是Swift编程的新手,尽管我可以为这个应用程序弄清楚其余的一切,但我一直在努力寻找如何推导可以处理此问题的算法的困难时期:

给出一组4个值(最好使用Double,因为有些甚至可以是分数),得出所有可能返回目标值结果的组合-在我的例子中为24。

例如,a + b + c + d,a + b + d + c,a + d + b + c,d + a + b + c以及该集合的所有排列,以及所有可能的数学运算符例如(a * b)^(cd)或(a + b + c)/ d。

我能够找到这篇文章,但与我要查找的内容并不完全匹配,特别是因为顺序对我来说并不重要 Number of ways to sum the items in a set to get a target value - Order matters

例如,我可以通过以下方式手动进行每种组合:

if a + b + c + d == 24 {
  print("\a + \b + \c + \d")
}

我的目标是让用户输入4个值,并让IOS生成所有可能产生24个数学表达式的列表。

帕特里克

2 个答案:

答案 0 :(得分:0)

前几条注释中引用的解决方案未涵盖的一种简单方法是在reverse polish notation(RPN)中生成候选表达式。如果您学习过CS或拥有HP计算器,您可能会想起这一点。 RPN的优点是它不包含括号,并且按从左到右的严格顺序进行评估。点击链接以获取完整描述,下面是几个示例:

  

代数:(a + b)* c

     

RPN:ab + c *

     
     

代数:a +(b * c)

     

RPN:abc * +

在大纲中从左到右评估RPN表达式,将找到的任何变量压入堆栈。对于任何运算符,您都将弹出堆栈的2个值,将其与操作结合,然后将结果推回堆栈中。

要为您的问题生成单个表达式,轮廓算法为:

<random>

这将为您提供一个表达式,使所有人员都想到递归。在每个阶段,您都要遍历上述所有选择,并为递归传递部分表达式,未使用的变量等的每个递归进行迭代。您可以将部分表达式存储在数组中,每个元素都是变量或运算符(请考虑{{1 }})。随着Swift在递归时按值传递数组,每个调用可以继续向数组添加元素,而不会影响其他调用。

使用伪代码:

while there are variables unused or fewer than (number of variables - 1) operators
   either:
      add any unused variable
   or:
      if the number of added variables is at least two greater than the
      number of added operators add any operator

生成完整表达式时,您可以对其进行求值,如果结果为24,则可以将其添加到解决方案中。作为一种可能的优化,您可以在进行递归操作时求值,以保存部分表达式的重新计算。 RPN评估可以使用堆栈,您可以从Swift中的数组构建堆栈,并在每次递归调用中向下传递。探索其他优化留给您。

如果在设计算法并编写一些代码后陷入困境,您可以提出一个新问题-包括指向该问题,算法,代码和问题的链接;无疑会有人帮助您。

HTH

答案 1 :(得分:0)

这是一种方法。从expressions数组和values数组开始。最初,expressions只是String的{​​{1}}值:

values

选择两个表达式(例如“ 1”和“ 2”,以及一个二进制运算符(“ +”)),并将它们组合在一起,以创建具有三个值的let expressions = ["1", "2", "3", "4"] let values = [1, 2, 3, 4] expressions

values

重复此过程,再次将两个表达式与一个运算结合起来:

expressions = ["3", "4", "(1 + 2)"]
values = [3, 4, 3]

最后,将最后两个表达式与“ +”组合:

expressions = ["3", "(4 + (1 + 2))"]
values = [3, 7]

一旦到达单个表达式,请检查该值是否与目标匹配。


下面是一个递归函数,它尝试对值和运算的所有组合来创建表达式以寻找目标:

expressions = ["(3 + (4 + (1 + 2)))"]
values = [10]

示例1:

// An array of tuples containing an operator name and a closure 
// that performs the operation
let ops: [(name: String, function: (Double, Double) -> Double)] = [
    ("+", { $0 + $1 }),
    ("-", { $0 - $1 }),
    ("*", { $0 * $1 }),
    ("/", { $0 / $1 }),
    ("^", { pow($0, $1) })
]


func compute(expressions: [String], values: [Double], target: Double) {

    // base case of the recursion: if there is only one
    // expression and one value, check if the value is the
    // target value we're looking for and print the expression
    // and value if the target is matched
    if expressions.count == 1 {
        if values[0] == target {
            print("\(expressions[0]) = \(values[0])")
        }
    } else if expressions.count >= 2 {

        // loop through all of the expressions choosing each
        // as the first expression
        for idx1 in expressions.indices {

            // copy the expressions and values arrays so that
            // we can remove the expression and value
            // without modifying the original arrays
            // which will be needed for the next try
            var expcopy = expressions
            var valcopy = values
            let exp1 = expcopy.remove(at: idx1)
            let val1 = valcopy.remove(at: idx1)

            // loop through the remaining expressions to find
            // the second one
            for idx2 in expcopy.indices {
                // again, copy the arrays to keep from modifying
                // the originals while searching
                var expcopy2 = expcopy
                var valcopy2 = valcopy

                let exp2 = expcopy2.remove(at: idx2)
                let val2 = valcopy2.remove(at: idx2)

                // now try all possible operations to combine
                // the two expressions
                for op in ops {
                    // use the closure to compute the value
                    let value = op.function(val1, val2)

                    // combine the expressions into a new string
                    // expression with the operator in the
                    // middle and surrounded by () if this is
                    // not the top level expression
                    var exp = "\(exp1) \(op.name) \(exp2)"
                    if !expcopy2.isEmpty {
                        exp = "(\(exp))"
                    }

                    // now that we've reduced the number of
                    // expressions by 1, recurse by calling
                    // compute again on the reduced list of
                    // expressions
                    compute(expressions: expcopy2 + [exp], values: valcopy2 + [value], target: target)
                }
            }
        }
    }
}


// This helper function creates the array of expressions from
// the array of values, and then calls the main function above
// to do the real work  
func search(values: [Double], target: Double) {
    compute(expressions: values.map(String.init), values: values, target: target)
}

输出:

search(values: [1, 2, 3, 4], target: 121)

示例2:

(1.0 - (3.0 * 4.0)) ^ 2.0 = 121.0
((3.0 * 4.0) - 1.0) ^ 2.0 = 121.0
(1.0 - (4.0 * 3.0)) ^ 2.0 = 121.0
((4.0 * 3.0) - 1.0) ^ 2.0 = 121.0

输出:

search(values: [1, 2, 3], target: 1)

消除重复的解决方案

具有4个或更多的值,或者甚至具有更少的非唯一值,您最终可能会得到重复的表达式。消除重复项的方法是使用3.0 / (1.0 + 2.0) = 1.0 (1.0 + 2.0) / 3.0 = 1.0 3.0 - (1.0 * 2.0) = 1.0 (1.0 ^ 2.0) ^ 3.0 = 1.0 (1.0 * 3.0) - 2.0 = 1.0 2.0 - (1.0 ^ 3.0) = 1.0 (1.0 ^ 3.0) ^ 2.0 = 1.0 3.0 / (2.0 + 1.0) = 1.0 (2.0 + 1.0) / 3.0 = 1.0 (2.0 - 1.0) ^ 3.0 = 1.0 3.0 - (2.0 * 1.0) = 1.0 3.0 - (2.0 / 1.0) = 1.0 3.0 - (2.0 ^ 1.0) = 1.0 1.0 ^ (2.0 + 3.0) = 1.0 1.0 ^ (2.0 - 3.0) = 1.0 1.0 ^ (2.0 * 3.0) = 1.0 1.0 ^ (2.0 / 3.0) = 1.0 1.0 ^ (2.0 ^ 3.0) = 1.0 2.0 / (3.0 - 1.0) = 1.0 (3.0 - 1.0) / 2.0 = 1.0 (3.0 * 1.0) - 2.0 = 1.0 (3.0 / 1.0) - 2.0 = 1.0 (3.0 ^ 1.0) - 2.0 = 1.0 1.0 ^ (3.0 + 2.0) = 1.0 1.0 * (3.0 - 2.0) = 1.0 1.0 / (3.0 - 2.0) = 1.0 1.0 ^ (3.0 - 2.0) = 1.0 (3.0 - 2.0) * 1.0 = 1.0 (3.0 - 2.0) / 1.0 = 1.0 (3.0 - 2.0) ^ 1.0 = 1.0 1.0 ^ (3.0 * 2.0) = 1.0 1.0 ^ (3.0 / 2.0) = 1.0 1.0 ^ (3.0 ^ 2.0) = 1.0 跟踪您已经找到的表达式,并在将其打印为新解决方案之前检查是否设置了Set<String>新表达式。

contains

示例:

// An array of tuples containing an operator name and a closure
// that performs the operation
let ops: [(name: String, function: (Double, Double) -> Double)] = [
    ("+", { $0 + $1 }),
    ("-", { $0 - $1 }),
    ("*", { $0 * $1 }),
    ("/", { $0 / $1 }),
    ("^", { pow($0, $1) })
]


func compute(expressions: [String], values: [Double], target: Double, solutions: inout Set<String>) {

    // base case of the recursion: if there is only one
    // expression and one value, check if the value is the
    // target value we're looking for and print the expression
    // and value if the target is matched and we don't already
    // have that expression in our set of solutions
    if expressions.count == 1 {
        if values[0] == target && !solutions.contains(expressions[0]) {
            print("\(expressions[0]) = \(values[0])")
            solutions.insert(expressions[0])
        }
    } else if expressions.count >= 2 {

        // loop through all of the expressions choosing each
        // as the first expression
        for idx1 in expressions.indices {

            // copy the expressions and values arrays so that
            // we can remove the expression and value
            // without modifying the original arrays
            // which will be needed for the next try
            var expcopy = expressions
            var valcopy = values

            let exp1 = expcopy.remove(at: idx1)
            let val1 = valcopy.remove(at: idx1)

            // loop through the remaining expressions to find
            // the second one
            for idx2 in expcopy.indices {
                // again, copy the arrays to keep from modifying
                // the originals while searching
                var expcopy2 = expcopy
                var valcopy2 = valcopy

                let exp2 = expcopy2.remove(at: idx2)
                let val2 = valcopy2.remove(at: idx2)

                // now try all possible operations to combine
                // the two expressions
                for op in ops {
                    // use the op's function to compute the value
                    let val = op.function(val1, val2)

                    // combine the expressions into a new string
                    // expression with the operator in the
                    // middle and surrounded by () if this is
                    // not the top level expression
                    var exp = "\(exp1) \(op.name) \(exp2)"
                    if !expcopy2.isEmpty {
                        exp = "(\(exp))"
                    }

                    // now that we've reduced the number of
                    // expressions by 1, recurse by calling
                    // compute again on the reduced list of
                    // expressions
                    let newexp = expcopy2 + [exp]
                    let newval = valcopy2 + [val]
                    compute(expressions: newexp, values: newval, target: target, solutions: &solutions)
                }
            }
        }
    }
}


// This helper function creates the array of expressions from
// the array of values, creates a Set to hold the solutions, and
// then calls the main function above to do the real work
func search(values: [Double], target: Double) {
    // create a set to keep track of solutions found so far
    var solutions = Set<String>()

    compute(expressions: values.map(String.init), values: values, target: target, solutions: &solutions)

    print("\n\(solutions.count) unique solutions were found")
}

输出:

search(values: [2, 2, 1], target: 5)