我是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个数学表达式的列表。
帕特里克
答案 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)