[英]understanding better a solution for finding permutations of a string - javascript
我试图更好地理解递归和函数式编程,我认为这是一个很好的实践示例,即使用递归和现代方法(如reduce,filter和map)创建字符串的排列。
我找到了这段漂亮的代码
const flatten = xs => xs.reduce((cum, next) => [...cum, ...next], []); const without = (xs, x) => xs.filter(y => y !== x); const permutations = xs => flatten(xs.map(x => xs.length < 2 ? [xs] : permutations(without(xs, x)).map(perm => [x, ...perm]) )); permutations([1,2,3]) // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
来自JavaScript的Permutations? 作者:MártonSári
为了添加一些控制台日志来调试它并理解它在幕后做了什么,我已经分隔了一点
const flatten = xs => { console.log(`input for flatten(${xs})`); return xs.reduce((cum, next) => { let res = [...cum, ...next]; console.log(`output from flatten(): ${res}`); return res; }, []); } const without = (xs, x) => { console.log(`input for without(${xs},${x})`) let res = xs.filter(y => y !== x); console.log(`output from without: ${res}`); return res; } const permutations = xs => { console.log(`input for permutations(${xs})`); let res = flatten(xs.map(x => { if (xs.length < 2) { return [xs] } else { return permutations(without(xs, x)).map(perm => [x, ...perm]) } })); console.log(`output for permutations: ${res}`) return res; } permutations([1,2,3])
我想我对每种方法的作用都有足够的了解,但我似乎无法概念化如何将它们组合起来创建[[1,2,3],[1,3,2],[2] ,1,3],[2,3,1],[3,1,2],[3,2,1]]
有人可以一步一步地告诉我发动机罩下发生了什么吗?
要获得所有权限,我们会执行以下操作:
我们从左到右取一个数组元素。
xs.map(x => // 1
对于所有其他元素,我们递归地生成排列:
permutations(without(xs, x)) // [[2, 3], [3, 2]]
对于每个排列,我们添加我们在开始时取回的值:
.map(perm => [xs, ...perm]) // [[1, 2, 3], [1, 3, 2]]
现在,对所有数组元素重复,结果是:
[
// 1
[[1, 2, 3], [1, 3, 2]],
// 2
[[2, 1, 3], [2, 3, 1]],
// 3
[[3, 1, 2], [3, 2, 1]]
]
现在我们只需要flatten(...)
该数组以获得所需的结果。
整个事情可以表示为递归调用树:
[1, 2, 3]
- [2, 3] ->
- [3] -> [1, 2, 3]
- [2] -> [1, 3, 2]
- [1, 3] ->
- [1] -> [2, 3, 1]
- [3] -> [2, 1, 3]
- [1, 2] ->
- [1] -> [3, 2, 1]
- [2] -> [3, 1, 2]
为了添加一些控制台日志来调试它,我稍微划了一下
这当然有帮助。 但请记住,简单的递归定义通常会导致复杂的执行跟踪。
这实际上是递归可能如此有用的原因之一。 因为某些算法具有复杂的迭代,所以允许简单的递归描述。 因此,理解递归算法的目标应该是在其定义中找出归纳(而不是迭代)推理。
让我们忘记javascript并专注于算法。 让我们看看我们可以得到集合A
的元素的排列,我们将表示P(A)
。
注意:在原始算法中输入是一个列表并不重要,因为原始顺序根本不重要。 同样,我们将返回一组列表而不是列表列表,这是无关紧要的,因为我们不关心计算解决方案的顺序。
基本情况:
最简单的情况是空集。 对于0个元素的排列,只有一个解决方案,该解决方案是空序列[]
。 所以,
P(A) = {[]}
递归案例:
为了使用递归,您想要描述如何从P(A')
获得P(A)
大小小于A
某些A'
。
注意:如果你这样做,它就完成了。 在操作上,程序将通过对具有越来越小的参数的P
连续调用来计算,直到它到达基本情况,然后它将从较短的结果中恢复较长的结果。
所以这里有一种方法来编写一个具有n + 1个元素的A
的特定排列。 您需要为每个位置连续选择A
一个元素:
_ _ ... _
n+1 n 1
所以你为第一个选择一个x ∈ A
x _ ... _
n 1
然后你需要在P(A\\{x})
选择一个排列。
这告诉你一种构建大小为n
所有排列的方法。 考虑的所有可能的选择x
在A
(作为第一元件使用),并且对于每个选择放x
中的每一溶液的前面P(A\\{x})
最后,为每个x
选择找到所有解决方案的联合。
让我们使用点运算符来表示将x
放在序列s
前面,并使用菱形运算符来表示将x
放在每个s ∈ S
前面。 那是,
x⋅s = [x, s1, s2, ..., sn]
x⟡S = {x⋅s : s ∈ S}
然后换一个非空A
P(A) = ⋃ {x⟡P(A\{x}) : x ∈ A}
此表达式与案例库一起为您提供集合A
中元素的所有排列。
javascript代码
要了解您显示的代码如何实现此算法,您需要考虑以下内容
当你有0或1个元素时,该代码通过编写xs.length < 2
考虑两个基本情况。 我们也可以这样做,这是无关紧要的。 您可以将2更改为1,它仍然可以工作。
映射对应于我们的运算x⟡S = {x⋅s : s ∈ S}
不对应P(A\\{x})
flatten对应于连接所有解决方案的⋃
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.