【问题标题】:Nested recursion, find all possible piece counts嵌套递归,找到所有可能的片数
【发布时间】:2017-09-14 14:49:57
【问题描述】:

给定一个示例输入:

[
    {"id":1,"currentBlack":1,"currentWhite":0,"max":1},
    {"id":2,"currentBlack":0,"currentWhite":1,"max":1},
]

输出输入的所有可能状态,其中 currentBlack 和 currentWhite 可以具有从其初始值到最大值范围内的任何值。

此示例的正确输出:

[
    [
        {"id":1,"currentBlack":1,"currentWhite":0,"max":1},
        {"id":2,"currentBlack":0,"currentWhite":1,"max":1},
    ],
    [
        {"id":1,"currentBlack":1,"currentWhite":1,"max":1},
        {"id":2,"currentBlack":0,"currentWhite":1,"max":1},
    ],
    [
        {"id":1,"currentBlack":1,"currentWhite":1,"max":1},
        {"id":2,"currentBlack":1,"currentWhite":1,"max":1},
    ],
    [
        {"id":1,"currentBlack":1,"currentWhite":0,"max":1},
        {"id":2,"currentBlack":1,"currentWhite":1,"max":1},
    ]
]

实际输入的最大值在 1 到 8 之间,并且输入数组中的对象要多得多。我的尝试如下(大量评论):

function allPossibleCounts(pieceCounts) {//pieceCounts is the input
        var collection = []; //used to collect all possible values
        recursiveCalls(pieceCounts); //runs recursive function
        return collection; //returns result

        function recursiveCalls(pieceCounts) {
            //if pieceCounts is already in collection then return, not yet implemented so duplicates are currently possible
            collection.push(pieceCounts);//inputs a potential value

            console.log(JSON.stringify(pieceCounts));//this is successfully logs the correct values
            console.log(JSON.stringify(collection));//collection isn't correct, all values at the top of the array are copies of each other

            for (let n in pieceCounts) {//pieceCounts should be the same at the start of each loop within each scope, aka pieceCounts should be the same at the end of this loop as it is at the start

                subBlackCall(pieceCounts);
                function subBlackCall(pieceCounts) {
                    if (pieceCounts[n].currentBlack < pieceCounts[n].max) {
                        pieceCounts[n].currentBlack++;//increment
                        recursiveCalls(pieceCounts);
                        subBlackCall(pieceCounts);//essentially you're either adding +1 or +2 or +3 ect all the way up to max and calling recursiveCalls() off of each of those incremented values
                        pieceCounts[n].currentBlack--;//decrement to return pieceCounts to how it was at the start of this function
                    }
                }

                subWhiteCall(pieceCounts);
                function subWhiteCall(pieceCounts) {
                    if (pieceCounts[n].currentWhite < pieceCounts[n].max) {
                        pieceCounts[n].currentWhite++;
                        recursiveCalls(pieceCounts);
                        subWhiteCall(pieceCounts);
                        pieceCounts[n].currentWhite--;
                    }
                }
            }
        }
    }

但目前我的尝试输出为复制数组的这种不敬的混乱

[[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}]]

编辑:工作代码:https://pastebin.com/qqFTppsY

【问题讨论】:

  • pieceCounts[n] 始终引用一个对象。您应该重新创建 pieceCount 以作为不同的对象保存到集合中。例如,您可以在recursiveCalls 函数的开头添加pieceCounts = JSON.parse(JSON.stringify(pieceCounts)); // just clone
  • 我认为这是一个指针问题,我不明白为什么使用 .slice() 从来没有用过,只是在一秒钟前才发现这是因为虽然它正在复制数组中的对象的指针数组都还是一样的。你应该把这个作为答案,它是正确的
  • 为什么不能有id: 1, currentBlack: 0, currentWhite: 0的组合?
  • 因为初始输入设置了下限,这样您就可以更轻松地递归遍历它,而不必在对象中有 min: 值。整个程序正在计算可以设置尺寸为 x,y 的棋盘的合法方式的数量。这是拼图中的最后一块,它为棋盘上的棋子数量生成了所有可能的组合。零不一定是我的特定程序的最低限度的原因是,您必须始终拥有 1 个黑色和 1 个白色国王才能使棋盘合法。还有其他限制,但这里适用

标签: javascript recursion permutation


【解决方案1】:

pieceCounts[n] 始终引用一个对象。您应该重新创建 pieceCount 以作为不同的对象保存到集合中。例如,您可以添加

pieceCounts = JSON.parse(JSON.stringify(pieceCounts)); // just clone 

recursiveCalls 函数的开头。

【讨论】:

    【解决方案2】:

    为避免转换为 JSON 并返回,我建议使用 Object.assign 结合阵列上的 map 执行更深的复制:

    function allPossibleCounts(pieceCounts) {
        var result = [],
            current = deeperCopy(pieceCounts);
    
        function deeperCopy(arr) {
            return arr.map( row => Object.assign({}, row) );
        }
    
        function recurse(depth) {
            // depth: indication of which value will be incremented. Each "row" has 
            // 2 items (black/white), so when depth is even, it refers to black, when 
            // odd to white. Divide by two for getting the "row" in which the increment
            // should happen.
            var idx = depth >> 1, // divide by 2 for getting row index
                prop = depth % 2 ? 'currentWhite' : 'currentBlack', // odd/even
                row = pieceCounts[idx];
            if (!row) { // at the end of the array
                // Take a copy of this variation and add it to the results
                result.push(deeperCopy(current));
                return; // backtrack for other variations
            }
            for (var value = row[prop]; value <= row.max; value++) {
                // Set the value of this property
                current[idx][prop] = value;
                // Collect all variations that can be made by varying any of 
                //   the property values that follow after this one
                recurse(depth+1);
                // Repeat for all higher values this property can get.
            }
        }
    
        recurse(0); // Start the process
        return result;
    }
    
    // Sample input
    var pieceCounts = [
        {"id":1,"currentBlack":1,"currentWhite":0,"max":1},
        {"id":2,"currentBlack":0,"currentWhite":1,"max":1},
    ];
    // Get results
    var result = allPossibleCounts(pieceCounts);
    // Output
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    这个想法是使用递归:想象可以解决除第一个属性之外的所有属性的所有变体的问题。产生这些,然后将第一个属性值更改为下一个可能的值。再次重复所有变化的产生,等等。所有这些结果的组合将是第一个属性值也应该变化的解决方案。

    这是递归的理想情况。当没有更多的属性值剩余时,递归停止:在这种情况下,只有一个解决方案;所有值都按原样设置的那个。可以添加到结果列表中。

    属性可以这样枚举:

    row  currentBlack   currentWhite
    ---------------------------------
     0       0               1
     1       2               3
     2       4               5
     3       6               7
                    ...
     n      2n-2            2n-1
    

    我们可以称这个数字为 depth,并在更深的递归的每一步增加它。给定深度,变化的属性定义为:

    depth is even  => currentBlack
    depth is odd   => currentWhite
    row number = depth / 2 (ignoring the remainder)
    

    【讨论】:

    • 与使用 object.assign 和 map 相比,转换为 JSON 并返回的效率如何?
    • 谢谢!这真的很有帮助,不过速度上的差异似乎差别很大
    • 所用时间还取决于您的浏览器和设备正在执行的其他操作,但平均而言,我在 Chrome 中看到 JSON 方式慢 15%,而在 Firefox 上,差异为 200% 或更多。这是相同的小提琴,但运行时间延长了 10 倍:jsfiddle.net/4dac4pf5/1
    • 所以有 3x3 x 1x1 x 2x2 x 3x3 x 3x3 x 3x3 = 9x1x4x9x9x9 = 26244 种可能性。我在该数据上运行了我的代码,它在我的笔记本电脑上大约半秒内生成了该数组长度。
    • 我以任何方式添加了解释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-08
    • 2014-03-14
    • 1970-01-01
    • 2023-03-16
    • 2022-01-05
    • 1970-01-01
    • 2021-07-08
    相关资源
    最近更新 更多