24游戏/倒计时/数字游戏解算器,但答案中没有括号

时间:2013-05-12 18:21:37

标签: algorithm math solver

我一直在浏览互联网,寻找一个现有的解决方案,从指定目标号码的数字和运营商列表中创建一个等式。

我遇到了很多24个游戏求解器,倒计时求解器等等,但它们都围绕着在答案中允许括号的概念。

例如,对于目标42,使用数字1 2 3 4 5 6,解决方案可以是:

6 * 5 = 30
4 * 3 = 12
30 + 12 = 42

注意算法如何记住子方程的结果,然后重新使用它来形成解(在本例中为30和12),主要使用括号来形成解(6 * 5) + (4 * 3) = 42

虽然我想要一个没有使用括号的解决方案,它从左到右解决,例如6 - 1 + 5 * 4 + 2 = 42,如果我把它写出来,那就是:

6 - 1 = 5
5 + 5 = 10
10 * 4 = 40
40 + 2 = 42

我有一个大约55个数字(随机数范围从2到12),9个运算符(每个基本运算符2个+ 1个随机运算符)和目标值(0到1000之间的随机数)的列表。我需要一个算法来检查我的目标值是否可以解决(并且可选地,如果不是,我们可以达到实际值的距离)。每个数字和运算符只能使用一次,这意味着最多可以使用10个数字来获得目标值。

我找到了一个蛮力算法,可以很容易地调整到我想做的事情(How to design an algorithm to calculate countdown style maths number puzzle),这有效,但我希望能找到一些产生更复杂的“解决方案”的东西,比如这个页面:http://incoherency.co.uk/countdown/

2 个答案:

答案 0 :(得分:3)

我写了你在帖子末尾提到的解算器,我提前道歉,代码不是很易读。

对于这类问题的任何解决方案的代码都只是一个深度优先搜索,你暗示你已经有了工作。

请注意,如果您使用“不使用括号的解决方案,这是从左到右解决”,那么有些输入集无法解决。例如,11,11,11,11,11,11,目标为144.解是((11/11)+11)*((11/11)+11)。我的求解器通过将括号分解为不同的行来使人类更容易理解,但它仍然有效地使用括号而不是从左到右进行评估。

“使用括号”的方法是对输入应用操作并将结果放回输入包中,而不是将操作应用于其中一个输入和累加器。例如,如果你的输入包是1,2,3,4,5,6并且你决定乘以3和4,那么这个包就变成了1,2,12,5,6。这样,当您递归时,该步骤可以使用上一步的结果。准备输出只是将操作历史与每个数字一起存储在包中。

我想你对更复杂的解决方案的意思只是我的javascript求解器中使用的简单启发式算法。解算器的工作原理是对整个搜索空间进行深度优先搜索,然后选择“最佳”的解决方案,而不仅仅是使用最少步骤的解决方案。

如果解决方案更接近目标,则解决方案被认为比以前的解决方案“更好”(即将其替换为“答案”解决方案)(请注意,解算器中的任何状态是候选者解决方案,只是大多数距离目标远远超过以前的最佳候选解决方案),或者它与目标距离相等并且具有较低的启发式分数。

启发式分数是“中间值”的总和(即“=”符号右侧的值),删除尾随0。例如,如果中间值为1,4,10,150,则启发式分数为1 + 4 + 1 + 15:10和150仅计数1和15,因为它们以零结尾。这样做是因为人们发现处理可被10整除的数字更容易,因此解决方案看起来“更简单”。

可被视为“复杂”的另一部分是某些线连接在一起的方式。这简单地将“5 + 3 = 8”和“8 + 2 = 10”的结果加入到“5 + 3 + 2 = 10”中。执行此操作的代码绝对可怕,但如果您对https://github.com/jes/cntdn/blob/master/js/cntdn.js中的Javascript感兴趣,那么要点是找到以数组形式存储的解决方案(有关每个数字的方式的信息)做了一堆后期处理。大致是:

  • 将从DFS生成的“解决方案列表”转换为(基本的,基于嵌套数组的)表达式树 - 这是为了应对多参数情况(即“5 + 3 + 2”不是2加法操作,它只是一个有3个参数的添加)
  • 将表达式树转换为一系列步骤,包括对参数进行排序,以便更一致地呈现它们
  • 将步骤数组转换为字符串表示形式以显示给用户,包括解释结果与目标数字的距离,如果不相等

道歉的长度。希望其中一些是有用的。

詹姆斯

编辑:如果你对一般的倒计时求解器感兴趣,你可能想看看我的字母求解器,因为它比第一个更优雅。它是https://github.com/jes/cntdn/blob/master/js/cntdn.js的前两个函数 - 使用带有字母串的函数调用solve_letters()和为每个匹配的单词调用的函数。此解算器通过遍历表示字典的trie(由https://github.com/jes/cntdn/blob/master/js/mk-js-dict生成),并在每个端节点调用回调来工作。

答案 1 :(得分:0)

我在java中使用递归来进行数组组合。主要思想是使用DFS来获得阵列组合和操作组合。

我使用布尔数组来存储访问位置,这可以避免再次使用相同的元素。临时StringBuilder用于存储当前方程,如果相应的结果等于target,我将把方程式放入结果中。当您选择下一个数组元素时,不要忘记将temp和visited数组返回到原始状态。

此算法会产生一些重复的答案,因此需要稍后进行优化。

public static void main(String[] args) {
    List<StringBuilder> res = new ArrayList<StringBuilder>();
    int[] arr = {1,2,3,4,5,6};
    int target = 42;
    for(int i = 0; i < arr.length; i ++){
        boolean[] visited = new boolean[arr.length];
        visited[i] = true;
        StringBuilder sb = new StringBuilder();
        sb.append(arr[i]);
        findMatch(res, sb, arr, visited, arr[i], "+-*/", target);
    }

    for(StringBuilder sb : res){
        System.out.println(sb.toString());
    }
}

public static void findMatch(List<StringBuilder> res, StringBuilder temp, int[] nums, boolean[] visited, int current, String operations, int target){
    if(current == target){
        res.add(new StringBuilder(temp));
    }

    for(int i = 0; i < nums.length; i ++){
        if(visited[i]) continue;
        for(char c : operations.toCharArray()){
            visited[i] = true;
            temp.append(c).append(nums[i]);
            if(c == '+'){
                findMatch(res, temp, nums, visited, current + nums[i], operations, target);
            }else if(c == '-'){
                findMatch(res, temp, nums, visited, current - nums[i], operations, target);
            }else if(c == '*'){
                findMatch(res, temp, nums, visited, current * nums[i], operations, target);
            }else if(c == '/'){
                findMatch(res, temp, nums, visited, current / nums[i], operations, target);
            }
            temp.delete(temp.length() - 2, temp.length());
            visited[i] = false;
        }
    }
}