javascript递归骰子组合并将结果存储在矩阵中

时间:2018-03-29 10:38:40

标签: javascript arrays recursion nested

我想要实现的是N-dices发布组合的经典结果之一,但是将结果保存在具有MN字段的矩阵中(其中N是骰子的数量,M是总数)可能组合的数量 - 通过6 ^ N获得。到目前为止,我已经编写了以下代码:

function Dice (commonFace, singleFace){
    this.diceFaces = ["critical", commonFace, commonFace, singleFace, "support1", "support2"]
    this.numCases = function(){
        return Math.pow(this.diceFaces.length, numberDices)
    }
}
//create the attack dice
var attackDice = new Dice("smash", "fury");

//create the defence dice
var defenceDice =  new Dice("block", "dodge");



//create a function that rolls the dice results and returns the number of results results
function rollDiceResults(diceTypeRolled, numberDicesRolled) {

    //total possible results of the rolls of that number of dices
    var totalPossibilites = diceTypeRolled.numCases(numberDicesRolled);

    //store the dice results
    var diceResults = new Array;


    function rollDice(diceType, iteration, array) {
        if (iteration == 1) {
            //return the base case
            for (i = 0; i < diceType.diceFaces.length; i++) {
                array[i] = (diceType.diceFaces[i]);
            }
        } else {
            //continue
            for (i = 0; i < diceType.diceFaces.length; i++) {

                array[i] = diceType.diceFaces[i];
                rollDice(diceType, iteration - 1, tempResult);
            }

        }
    }


    for (i = 0; i < numberDicesRolled; i++) {
        rollDice(diceTypeRolled, numberDicesRolled, diceResults);
    }

}

我得到的是

  • 函数声明中的错误
  • 我想知道如何在保持m-n结构的同时调用函数内的数组

感谢您的帮助

1 个答案:

答案 0 :(得分:1)

固定长度组合

Recursion是一种功能性遗产,因此将其与功能风格一起使用将产生最佳效果。递归就是将一个大问题分解为较小的子问题,直到达到基本情况

下面,我们使用建议的Array.prototype.flatMap,但是对于尚不支持它的环境包含polyfill。当达到n = 0我们的基本情况时,我们返回空结果。归纳案例为n > 0,其中choices将添加到较小问题combination (choices, n - 1)的结果中 - 我们说此问题较小,因为{{1} }更接近n - 1

的基本情况

n = 0

在您的计划中使用Array.prototype.flatMap = function (f) { return this.reduce ((acc, x) => acc.concat (f (x)), []) } const combinations = (choices, n = 1) => n === 0 ? [[]] : combinations (choices, n - 1) .flatMap (comb => choices .map (c => [ c, ...comb ])) const faces = [ 1, 2, 3 ] // roll 2 dice console.log (combinations (faces, 2)) // [ [ 1, 1 ], [ 2, 1 ], [ 3, 1 ], [ 1, 2 ], ..., [ 2, 3 ], [ 3, 3 ] ] // roll 3 dice console.log (combinations (faces, 3)) // [ [ 1, 1, 1 ], [ 2, 1, 1 ], [ 3, 1, 1 ], [ 1, 2, 1 ], ..., [ 2, 3, 3 ], [ 3, 3, 3 ] ]

combinations看起来像这样

rollDice

没有依赖

如果您对const rollDice = (dice, numberOfDice) => combinations (dice.diceFaces, numberOfDice) console.log (rollDice (attackDice, 2)) // [ [ 'critical', 'critical' ] // , [ 'smash', 'critical' ] // , [ 'smash', 'critical' ] // , [ 'fury', 'critical' ] // , [ 'support1', 'critical' ] // , [ 'support2', 'critical' ] // , [ 'critical', 'smash' ] // , [ 'smash', 'smash' ] // , ... // , [ 'critical', 'support2' ] // , [ 'smash', 'support2' ] // , [ 'smash', 'support2' ] // , [ 'fury', 'support2' ] // , [ 'support1', 'support2' ] // , [ 'support2', 'support2' ] // ] flatMap的工作方式感到好奇,我们可以自行实施。纯粹的递归,贯穿始终。

map

<强>削弱

好的,const None = Symbol () const map = (f, [ x = None, ...xs ]) => x === None ? [] : [ f (x), ...map (f, xs) ] const flatMap = (f, [ x = None, ...xs ]) => x === None ? [] : [ ...f (x), ...flatMap (f, xs) ] const combinations = (choices = [], n = 1) => n === 0 ? [[]] : flatMap ( comb => map (c => [ c, ...comb ], choices) , combinations (choices, n - 1) ) const faces = [ 1, 2, 3 ] // roll 2 dice console.log (combinations (faces, 2)) // [ [ 1, 1 ], [ 2, 1 ], [ 3, 1 ], [ 1, 2 ], ..., [ 2, 3 ], [ 3, 3 ] ] // roll 3 dice console.log (combinations (faces, 3)) // [ [ 1, 1, 1 ], [ 2, 1, 1 ], [ 3, 1, 1 ], [ 1, 2, 1 ], ..., [ 2, 3, 3 ], [ 3, 3, 3 ] ]允许我们确定重复的固定选项的可能组合。如果我们有2个独特的骰子并希望得到所有可能的卷?

combinations

我们可以调用const results = rollDice (attackDice, defenceDice) ??? 然后调用rollDice (attackDice, 1),然后以某种方式组合答案。但是有更好的方法;一种允许任意数量的独特骰子的方式,即使每个骰子上有不同数量的边。下面,我向您展示我们编写的rollDice (defenceDice, 1)的两个版本以及必要的更改以访问尚未开发的潜力

combinations

使用这个新版本的 // version 1: using JS natives const combinations = (choices, n = 1) => const combinations = (choices = None, ...rest) => n === 0 choices === None ? [[]] : combinations (choices, n - 1) .flatMap (comb => : combinations (...rest) .flatMap (comb => choices .map (c => [ c, ...comb ])) // version 2: without dependencies const combinations = (choices = [], n = 1) => const combinations = (choices = None, ...rest) => n === 0 choices === None ? [[]] : flatMap ( comb => map (c => [ c, ...comb ], choices) , combinations (choices, n - 1) , combinations (...rest) ),我们可以滚动任意大小的任意数量的骰子 - 即使是在这个程序中物理上不可能的3面骰子也是可能的^ _ ^

combinations

当然,你可以滚动同一个骰子的集合

// version 3: variadic dice
const combinations = (choices = None, ...rest) =>
  choices === None
    ? [[]]
    : flatMap ( comb => map (c => [ c, ...comb ], choices)
              , combinations (...rest)
              )

const d1 =
  [ 'J', 'Q', 'K' ]

const d2 =
  [ '♤', '♡', '♧', '♢' ]

console.log (combinations (d1, d2))
// [ [ 'J', '♤' ], [ 'Q', '♤' ], [ 'K', '♤' ]
// , [ 'J', '♡' ], [ 'Q', '♡' ], [ 'K', '♡' ]
// , [ 'J', '♧' ], [ 'Q', '♧' ], [ 'K', '♧' ]
// , [ 'J', '♢' ], [ 'Q', '♢' ], [ 'K', '♢' ]
// ]

利用您的计划挖掘这种潜力,您可以将console.log (combinations (d1, d1, d1)) // [ [ 'J', 'J', 'J' ] // , [ 'Q', 'J', 'J' ] // , [ 'K', 'J', 'J' ] // , [ 'J', 'Q', 'J' ] // , [ 'Q', 'Q', 'J' ] // , [ 'K', 'Q', 'J' ] // , [ 'J', 'K', 'J' ] // , ... // , [ 'K', 'Q', 'K' ] // , [ 'J', 'K', 'K' ] // , [ 'Q', 'K', 'K' ] // , [ 'K', 'K', 'K' ] // ] 写为

rollDice

或与各种骰子

const rollDice = (...dice) =>
  combinations (...dice.map (d => d.diceFaces))

console.log (rollDice (attackDice, defenceDice))
// [ [ 'critical', 'critical' ]
// , [ 'smash', 'critical' ]
// , [ 'smash', 'critical' ]
// , [ 'fury', 'critical' ]
// , [ 'support1', 'critical' ]
// , [ 'support2', 'critical' ]
// , [ 'critical', 'block' ]
// , [ 'smash', 'block' ]
// , ...
// , [ 'support2', 'support1' ]
// , [ 'critical', 'support2' ]
// , [ 'smash', 'support2' ]
// , [ 'smash', 'support2' ]
// , [ 'fury', 'support2' ]
// , [ 'support1', 'support2' ]
// , [ 'support2', 'support2' ]
// ]

走高级

很高兴看到我们如何通过JavaScript中的一些纯函数来实现这一目标。然而,上述实施是缓慢的,并且在它可以产生多少组合方面受到严重限制。

下面,我们尝试确定七个6面骰子的组合。我们预计6 ^ 7会产生279936种组合

const rollDice = (...dice) =>
  combinations (...dice.map (d => d.diceFaces))

console.log (rollDice (defenceDice, attackDice, attackDice, attackDice))
// [ [ 'critical', 'critical', 'critical', 'critical' ]
// , [ 'block', 'critical', 'critical', 'critical' ]
// , [ 'block', 'critical', 'critical', 'critical' ]
// , [ 'dodge', 'critical', 'critical', 'critical' ]
// , [ 'support1', 'critical', 'critical', 'critical' ]
// , [ 'support2', 'critical', 'critical', 'critical' ]
// , [ 'critical', 'smash', 'critical', 'critical' ]
// , [ 'block', 'smash', 'critical', 'critical' ]
// , [ 'block', 'smash', 'critical', 'critical' ]
// , [ 'dodge', 'smash', 'critical', 'critical' ]
// , [ 'support1', 'smash', 'critical', 'critical' ]
// , ...
// ]

根据您在上面选择的const dice = [ attackDice, attackDice, attackDice, attackDice, attackDice, attackDice, attackDice ] rollDice (...dice) // => ... 的实现,如果它不会导致您的环境无限期挂起,则会导致堆栈溢出错误

为了提高效果,我们提供了Javascript提供的高级功能:generators。下面,我们重写combinations,但这次使用了与生成器交互所需的一些命令式样式。

combinations

上面,我们使用const None = Symbol () const combinations = function* (...all) { const loop = function* (comb, [ choices = None, ...rest ]) { if (choices === None) return else if (rest.length === 0) for (const c of choices) yield [ ...comb, c ] else for (const c of choices) yield* loop ([ ...comb, c], rest) } yield* loop ([], all) } const d1 = [ 'J', 'Q', 'K', 'A' ] const d2 = [ '♤', '♡', '♧', '♢' ] const result = Array.from (combinations (d1, d2)) console.log (result) // [ [ 'J', '♤' ], [ 'J', '♡' ], [ 'J', '♧' ], [ 'J', '♢' ] // , [ 'Q', '♤' ], [ 'Q', '♡' ], [ 'Q', '♧' ], [ 'Q', '♢' ] // , [ 'K', '♤' ], [ 'K', '♡' ], [ 'K', '♧' ], [ 'K', '♢' ] // , [ 'A', '♤' ], [ 'A', '♡' ], [ 'A', '♧' ], [ 'A', '♢' ] // ]热切地将所有组合收集到一个Array.from中。使用发电机时通常不需要这样做。相反,我们可以使用值作为生成它们

下面,我们使用for...of直接与每个组合进行交互,因为它来自生成器。在此示例中,我们显示包含resultJ

的任意组合

但当然这里有更多的潜力。我们可以在const d1 = [ 'J', 'Q', 'K', 'A' ] const d2 = [ '♤', '♡', '♧', '♢' ] for (const [ rank, suit ] of combinations (d1, d2)) { if (rank === 'J' || suit === '♡' ) console.log (rank, suit) } // J ♤ <-- all Jacks // J ♡ // J ♧ // J ♢ // Q ♡ <-- or non-Jacks with Hearts // K ♡ // A ♡ 块中写出我们想要的任何内容。下面,我们使用for

跳过 Queens Q添加了一个附加条件
continue

也许这里最强大的是我们可以停止const d1 = [ 'J', 'Q', 'K', 'A' ] const d2 = [ '♤', '♡', '♧', '♢' ] for (const [ rank, suit ] of combinations (d1, d2)) { if (rank === 'Q') continue if (rank === 'J' || suit === '♡' ) console.log (rank, suit) } // J ♤ // J ♡ // J ♧ // J ♢ // K ♡ <--- Queens dropped from the output // A ♡ 生成组合。下面,如果遇到国王break,我们立即停止发电机

K

您可以根据条件获得相当的创意。如何以心脏开始或结束的所有组合

const d1 =
  [ 'J', 'Q', 'K', 'A' ]

const d2 =
  [ '♤', '♡', '♧', '♢' ]

for (const [ rank, suit ] of combinations (d1, d2))
{
  if (rank === 'K')
    break
  if (rank === 'J' || suit === '♡' )
    console.log (rank, suit)
}
// J ♤
// J ♡
// J ♧
// J ♢
// Q ♡ <-- no Kings or Aces; generator stopped at K

并向您展示生成器适用于大型数据集

for (const [ a, b, c, d, e ] of combinations (d2, d2, d2, d2, d2))
{
  if (a === '♡' && e === '♡')
    console.log (a, b, c, d, e)
}

// ♡ ♤ ♤ ♤ ♡
// ♡ ♤ ♤ ♡ ♡
// ♡ ♤ ♤ ♧ ♡
// ...
// ♡ ♢ ♢ ♡ ♡
// ♡ ♢ ♢ ♧ ♡
// ♡ ♢ ♢ ♢ ♡

我们甚至可以编写高阶函数来处理生成器,例如我们自己的const d1 = [ 1, 2, 3, 4, 5, 6 ] Array.from (combinations (d1, d1, d1, d1, d1, d1, d1)) .length // 6^7 = 279936 Array.from (combinations (d1, d1, d1, d1, d1, d1, d1, d1)) .length // 6^8 = 1679616 函数。下面,我们找到三个20面骰子的所有组合,形成Pythagorean triple - 3个整数,构成有效直角三角形的边长

filter

或者使用const filter = function* (f, iterable) { for (const x of iterable) if (f (x)) yield x } const d20 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ] const combs = combinations (d20, d20, d20) const pythagoreanTriple = ([ a, b, c ]) => (a * a) + (b * b) === (c * c) for (const c of filter (pythagoreanTriple, combs)) console.log (c) // [ 3, 4, 5 ] // [ 4, 3, 5 ] // [ 5, 12, 13 ] // [ 6, 8, 10 ] // [ 8, 6, 10 ] // [ 8, 15, 17 ] // [ 9, 12, 15 ] // [ 12, 5, 13 ] // [ 12, 9, 15 ] // [ 12, 16, 20 ] // [ 15, 8, 17 ] // [ 16, 12, 20 ] 和映射函数同时将每个组合转换为新结果并将所有结果收集到数组中

Array.from

功能是什么?

功能编程很深入。潜入!

const allResults =
  Array.from ( filter (pythagoreanTriple, combs)
             , ([ a, b, c ], index) => ({ result: index + 1, solution: `${a}² + ${b}² = ${c}²`})
             )

console.log (allResults)
// [ { result: 1, solution: '3² + 4² = 5²' }
// , { result: 2, solution: '4² + 3² = 5²' }
// , { result: 3, solution: '5² + 12² = 13²' }
// , ...
// , { result: 10, solution: '12² + 16² = 20²' }
// , { result: 11, solution: '15² + 8² = 17²' }
// , { result: 12, solution: '16² + 12² = 20²' }
// ]