递归
我们可以一厢情愿地实现generatePairs——
const generatePairs = (n = 0) =>
chooseN(2, range(0, n))
range 是一个简单的函数,它生成一个从a 到b 的数组-
const range = (a = 0, b = 0) =>
a > b // base
? []
: [ a, ...range(a + 1, b) ] // inductive: a <= b
而chooseN 是一个泛型,它从数组a 中生成所有n 大小的样本-
const chooseN = (n = 0, a = []) =>
n <= 0 // base
? [[]]
: a.length <= 0 // inductive: n > 0
? []
: chooseN(n - 1, a) // inductive: n > 0, non-empty a
.map(r => [a[0], ...r])
.concat(chooseN(n, a.slice(1)))
请参阅下面的 generatePairs 在您自己的浏览器中工作 -
const range = (a = 0, b = 0) =>
a > b
? []
: [ a, ...range(a + 1, b) ]
const chooseN = (n = 0, a = []) =>
n <= 0
? [[]]
: a.length <= 0
? []
: chooseN(n - 1, a)
.map(r => [a[0], ...r])
.concat(chooseN(n, a.slice(1)))
const generatePairs = (n = 0) =>
chooseN(2, range(0, n))
const log = x =>
console.log(JSON.stringify(x))
log(generatePairs(3))
// [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
log(generatePairs(2))
// [[0,0],[0,1],[0,2],[1,1],[1,2],[2,2]]
log(generatePairs(1))
// [[0,0],[0,1],[1,1]]
log(generatePairs(0))
// [[0,0]]
发电机
由于组合问题通常涉及较大的解决方案空间,因此通常会懒惰地生成组合。在 JavaScript 中,我们可以使用生成器来做到这一点 -
const chooseN = function* (n = 0, a = [])
{ if (n <= 0)
return yield []
if (a.length <= 0)
return
for (const r of chooseN(n - 1, a))
yield [a[0], ...r]
yield* chooseN(n, a.slice(1))
}
请注意此程序与上述程序在结构上的相似性 -
const chooseN = function* (n = 0, a = [])
{ if (n <= 0) // if (n <= 0)
return yield [] // return [[]]
if (a.length <= 0) // else if (a.length <= 0)
return // return []
// else return:
for (const r of chooseN(n - 1, a)) // chooseN(n - 1, a).map(r =>
yield [a[0], ...r] // [a[0],...r])
yield* chooseN(n, a.slice(1)) // .concat(chooseN(n, a.slice(1)))
}
例如,我们可以编写一个solver 来找到第一对[ a, b ],其中a > 3 和3*a 等于2*b。至关重要的是,在找到第一个解决方案后不会生成任何对 -
const solver = (size = 0) =>
{ for(const [a, b] of generatePairs(size))
if (a > 3)
if (3 * a === 2 * b)
return [a, b]
}
console.log(solver(10))
// [ 4, 6 ]
而a = 4、b = 6 的解法是正确的:4 > 3 为真且3*4 等于2*6 (12)。
下面,如果我们愿意,我们可以使用 Array.from 生成整个对数组 -
const allPairs =
Array.from(generatePairs(3)) // <-- Array.from exhausts an iterable
console.log(allPairs)
// [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
扩展下面的 sn-p 以使用 JavaScript 的生成器生成对 -
const range = (a = 0, b = 0) =>
a > b
? []
: [ a, ...range(a + 1, b) ]
const chooseN = function* (n = 0, a = [])
{ if (n <= 0)
return yield []
if (a.length <= 0)
return
for (const r of chooseN(n - 1, a))
yield [a[0], ...r]
yield* chooseN(n, a.slice(1))
}
const generatePairs = (n = 0) =>
Array.from(chooseN(2, range(0, n)))
const log = x =>
console.log(JSON.stringify(x))
log(generatePairs(3))
// [[0,0],[0,1],[0,2],[0,3],[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
log(generatePairs(2))
// [[0,0],[0,1],[0,2],[1,1],[1,2],[2,2]]
log(generatePairs(1))
// [[0,0],[0,1],[1,1]]
log(generatePairs(0))
// [[0,0]]