【问题标题】:Is there a way in Swift to calculate all of the possible ways 3 numbers can be added, multiplied, subtracted or divided. Including parenthesesSwift 中有没有一种方法可以计算 3 个数字的加法、乘法、减法或除法的所有可能方法。包括括号
【发布时间】:2020-08-19 22:18:49
【问题描述】:

我正在用 Xcode 开发一个游戏,需要找出 3 个数字的所有可能数学结果。玩家试图使用这三个数字来尽可能接近目标数字。例如,如果目标是 10,而你的三个数字是 8、6 和 4,你可以使用 8 + 6 - 4。如果目标数字是 12,你可以使用 8*6/4 并得到 12。我目前是手动运行 142 种可能的组合并将结果存储在一个数组中:

resultsArray[0] = firstNum+secondNum+thirdNum; 
resultsArray[1] = firstNum+secondNum-thirdNum; 
resultsArray[2] = firstNum+secondNum*thirdNum; 
resultsArray[3] = firstNum+secondNum/thirdNum; 
...
resultsArray[143] = thirdNum/(secondNum-firstNum);

然后我正在检查数组中最接近的正确答案,如下所示:

let x = 10 //target number
let closest = resultsArray.enumerated().min( by: { abs($0.1 - x) < abs($1.1 - x) } )!
correctAnswer = resultsArray[closest.offset] 

它可以工作,除了除以零错误。

我知道有更好的方法,但我在网上搜索了一下,结果空手而归。 有什么想法吗?

【问题讨论】:

  • 您关心哪个公式提供最接近的结果,还是只需要知道数字?如果有多个同样接近的值,你想做什么?

标签: swift kotlin combinations permutation


【解决方案1】:

这是一个有趣的问题,让您可以使用 Swift 的自定义枚举、可选、集合、高阶函数 (map) 以及从函数返回多个值的能力。

144 个方程!即使我们有代码,也很难确认您已经涵盖了所有内容。除以零是一个棘手的情况,需要特别注意。

这是我对这个问题的看法。我的解决方案的目标是:

  1. 将其分解为易于验证的步骤
  2. 避免被零除
  3. 避免小数计算
  4. 避免负数
  5. 以人类可读的形式显示方程
  6. 找出最接近目标的所有方程
// Generate all 6 permuations of the 3 numbers.
// Use Set() to remove duplicates
func permuteNumbers(_ a: Int, _ b: Int, _ c: Int) -> [(Int, Int, Int)] {
    return Set([[a, b, c],
                [a, c, b],
                [b, a, c],
                [b, c, a],
                [c, a, b],
                [c, b, a]]).map { ($0[0], $0[1], $0[2]) }
}

enum Operation: String, CaseIterable {
    case addition = "+"
    case subtraction = "-"
    case multiplication = "*"
    case division = "/"
}

// Generate all 16 combinations of the 4 operations
func allOperations() -> [(Operation, Operation)] {
    var all = [(Operation, Operation)]()
    for op1 in Operation.allCases {
        for op2 in Operation.allCases {
            all.append((op1, op2))
        }
    }
    return all
}

// Return nil on divide by zero.
// Return nil when the result would be a negative number.
// Return nil when the result would be a fraction (not a whole number).
func performOperation(_ a: Int, _ b: Int, _ op: Operation) -> Int? {
    switch op {
    case .addition:        return a + b
    case .subtraction:     return (b > a) ? nil : a - b
    case .multiplication:  return a * b
    case .division:        return ((b == 0) || (a % b != 0)) ? nil : a / b
    }
}

// Perform (a op1 b) op2 c
// return (result, equation)
func performOp1First(a: Int, b: Int, c: Int, op1: Operation, op2: Operation) -> (Int?, String) {
    let str = "(\(a) \(op1.rawValue) \(b)) \(op2.rawValue) \(c)"
    
    if let r1 = performOperation(a, b, op1) {
        if let r2 = performOperation(r1, c, op2) {
            return (r2, str)
        }
    }
    return (nil, str)
}

// Perform a op1 (b op2 c)
// return (result, equation)
func performOp2First(a: Int, b: Int, c: Int, op1: Operation, op2: Operation) -> (Int?, String) {
    let str = "\(a) \(op1.rawValue) (\(b) \(op2.rawValue) \(c))"

    if let r1 = performOperation(b, c, op2) {
        if let r2 = performOperation(a, r1, op1) {
            return (r2, str)
        }
    }
    return (nil, str)
}

// Perform a op1 b op2 c - order doesn't matter for (+, +), (+, -), (*, *), and (*, /)
// return (result, equation)
func performNoParens(a: Int, b: Int, c: Int, op1: Operation, op2: Operation) -> (Int?, String) {
    let str = "\(a) \(op1.rawValue) \(b) \(op2.rawValue) \(c)"
    
    if let r1 = performOperation(a, b, op1) {
        if let r2 = performOperation(r1, c, op2) {
            return (r2, str)
        }
    }
    return (nil, str)
}

// Search all permutations of the numbers, operations, and operation order
func findBest(a: Int, b: Int, c: Int, target: Int) -> (diff: Int, equations: [String]) {
    let numbers = permuteNumbers(a, b, c)
    
    var best = Int.max
    var equations = [String]()
    
    for (a, b, c) in numbers {
        for (op1, op2) in allOperations() {
            // Parentheses are not needed if the operators are (+, +), (+, -), (*, *), (*, /)
            let noparens = [["+", "+"], ["+", "-"],["*", "*"], ["*", "/"]].contains([op1.rawValue, op2.rawValue])
            
            for f in (noparens ? [performNoParens] : [performOp1First, performOp2First]) {
                let (result, equation) = f(a, b, c, op1, op2)
                if let result = result {
                    let diff = abs(result - target)
                    if diff == best {
                        equations.append(equation)
                    } else if diff < best {
                        best = diff
                        equations = [equation]
                    }
                }
            }
        }
    }
    
    return (best, equations)
}

示例:

print(findBest(a: 8, b: 6, c: 4, target: 10))
(diff: 0, equations: ["8 + 6 - 4", "6 + 8 - 4", "(6 - 4) + 8", "(8 - 4) + 6"])
print(findBest(a: 8, b: 6, c: 4, target: 12))
(diff: 0, equations: ["6 * 8 / 4", "8 * 6 / 4", "(8 / 4) * 6"])
print(findBest(a: 8, b: 6, c: 4, target: 4))
(diff: 0, equations: ["6 - (8 / 4)", "8 / (6 - 4)"])
print(findBest(a: 8, b: 6, c: 4, target: 5))
(diff: 1, equations: ["6 - (8 / 4)", "4 + 8 - 6", "(8 - 6) + 4", "8 - (6 - 4)", "8 / (6 - 4)", "8 + 4 - 6"])
print(findBest(a: 8, b: 6, c: 4, target: 7))
(diff: 1, equations: ["(8 - 6) + 4", "8 - (6 - 4)", "(8 - 6) * 4", "6 + (8 / 4)", "4 + 8 - 6", "4 * (8 - 6)", "8 + 4 - 6", "(8 / 4) + 6"])

Kotlin 版本

这是一个从 Swift 版本手动翻译的 Kotlin 版本。这是我的第一个 Kotlin 程序,所以我确信我没有以最惯用的方式做所有事情。我在Online Kotlin Playground测试了这个程序

import kotlin.math.abs

// Generate all 6 permuations of the 3 numbers.
// Use Set() to remove duplicates
fun permuteNumbers(a: Int, b: Int, c: Int): Set<List<Int>> {
    return setOf(
        listOf(a, b, c),
        listOf(a, c, b),
        listOf(b, a, c),
        listOf(b, c, a),
        listOf(c, a, b),
        listOf(c, b, a)
    )
}

enum class Operation(val string: String) { 
  ADDITION("+"), 
  SUBTRACTION("-") ,
  MULTIPLICATION("*"),
  DIVISION("/")
}

fun allOperations(): List<Pair<Operation, Operation>> {
    val result = mutableListOf<Pair<Operation, Operation>>()
    for (op1 in Operation.values()) {
        for (op2 in Operation.values()) {
            result.add(Pair(op1, op2))
        }
    }
    
    return result
}

fun performOperation(a: Int, b: Int, op: Operation): Int? {
    return when (op) {
        Operation.ADDITION       ->  (a + b)
        Operation.SUBTRACTION    ->  if (b > a) { null } else { a - b }
        Operation.MULTIPLICATION ->  a * b
        Operation.DIVISION       ->  if ((b == 0) || (a % b != 0)) { null} else { a / b }
    }
}

// Perform (a op1 b) op2 c
// return (result, equation)
fun performOp1First(a: Int, b: Int, c: Int, op1: Operation, op2: Operation): Pair<Int?, String> {
    val str = "($a ${op1.string} $b) ${op2.string} $c"
    
    performOperation(a, b, op1)?.also { r1 ->
        performOperation(r1, c, op2)?.also { r2 ->
            return Pair(r2, str)
        }
    }
    return Pair(null, str)
}

// Perform a op1 (b op2 c)
// return (result, equation)
fun performOp2First(a: Int, b: Int, c: Int, op1: Operation, op2: Operation): Pair<Int?, String> {
    val str = "$a ${op1.string} ($b ${op2.string} $c)"
    
    performOperation(b, c, op2)?.also { r1 ->
        performOperation(a, r1, op1)?.also { r2 ->
            return Pair(r2, str)
        }
    }
    return Pair(null, str)
}

// Perform a op1 b op2 c - order doesn't matter for (+, +), (+, -), (*, *), and (*, /)
// return (result, equation)
fun performNoParens(a: Int, b: Int, c: Int, op1: Operation, op2: Operation): Pair<Int?, String> {
    val str = "$a ${op1.string} $b ${op2.string} $c"
    
    performOperation(a, b, op1)?.also { r1 ->
        performOperation(r1, c, op2)?.also { r2 ->
            return Pair(r2, str)
        }
    }
    return Pair(null, str)
}

// Search all permutations of the numbers, operations, and operation order
fun findBest(a: Int, b: Int, c: Int, target: Int): Pair<Int, List<String>> {
    val numbers = permuteNumbers(a, b, c)
    
    var best = Int.MAX_VALUE
    var equations = mutableListOf<String>()
    
    for ((a1, b1, c1) in numbers) {
        for ((op1, op2) in allOperations()) {
            // Parentheses are not needed if the operators are (+, +), (+, -), (*, *), (*, /)
            val noparens = listOf(listOf("+", "+"), listOf("+", "-"), listOf("*", "*"), listOf("*", "/"))
                .contains(listOf(op1.string, op2.string))
            
            for (f in if (noparens) { listOf(::performNoParens) } else { listOf(::performOp1First, ::performOp2First) }) {
                val (result, equation) = f(a1, b1, c1, op1, op2)
                result?.also { result2 ->
                    val delta = abs(target - result2)
                    if (delta == best) {
                        equations.add(equation)
                    } else if (delta < best) {
                        best = delta
                        equations = mutableListOf(equation)
                    }
                }
            }
            
        }
    }
    
    return Pair(best, equations)
}

fun main() {
    println(findBest(4, 6, 8, 4))
}

【讨论】:

  • 这太棒了!非常感谢。但是有一个问题,我应该在游戏描述中解释。我只想显示导致整数的方程式。例如: print(findBest(a: 8, b: 6, c: 4, target: 5)) 最好的解决方案是 8 - 6 + 4 。差异将是一个。导致小数的解决方案太难了。有没有办法修改它以仅找到导致孔数的解决方案?
  • 我可以假设输入的数字也是整数吗?像 8 * (1 / 2) 这样的等式也可以吗,因为即使在计算过程中有 0.5,答案也是整数 4?
  • 是的,所有输入的数字都是 1 到 40 之间的整数。8 * (1 / 2) 不行,因为 1/2 不会产生整数。我们试图只使用数字可以直接相互整除的方程。这个游戏是为小学生准备的。再次感谢!
  • 我认为这可以通过为不返回整数的除法返回 nil 来实现。也就是说,我们会将这些分数除法视为除以零并拒绝这些答案。另外,我认为我们只能使用整数数学。
  • 避免负数怎么样? 8 + (4 - 6) 是否应该被拒绝而 (8 + 4) - 6 应该受到青睐?
猜你喜欢
  • 1970-01-01
  • 2019-12-27
  • 2018-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多