简体   繁体   English

更好地理解查找字符串排列的解决方案 - javascript

[英]understanding better a solution for finding permutations of a string - javascript

I'm trying to get a better understanding of recursion as well as functional programming, I thought a good practice example for that would be to create permutations of a string with recursion and modern methods like reduce, filter and map. 我试图更好地理解递归和函数式编程,我认为这是一个很好的实践示例,即使用递归和现代方法(如reduce,filter和map)创建字符串的排列。

I found this beautiful piece of code 我找到了这段漂亮的代码

 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]] 

from Permutations in JavaScript? 来自JavaScript的Permutations? by Márton Sári 作者:MártonSári

I've delimited it a bit in order to add some console logs to debug it and understand what's it doing behind the scenes 为了添加一些控制台日志来调试它并理解它在幕后做了什么,我已经分隔了一点

 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]) 

I think I have a good enough idea of what each method iss doing, but I just can't seem to conceptualize how it all comes together to create [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] 我想我对每种方法的作用都有足够的了解,但我似乎无法概念化如何将它们组合起来创建[[1,2,3],[1,3,2],[2] ,1,3],[2,3,1],[3,1,2],[3,2,1]]

can somebody show me step by step what's going on under the hood? 有人可以一步一步地告诉我发动机罩下发生了什么吗?

To get all permuations we do the following: 要获得所有权限,我们会执行以下操作:

We take one element of the array from left to right. 我们从左到右取一个数组元素。

 xs.map(x => // 1

For all the other elements we generate permutations recursively: 对于所有其他元素,我们递归地生成排列:

 permutations(without(xs, x)) // [[2, 3], [3, 2]]

for every permutation we add the value we've taken out back at the beginning: 对于每个排列,我们添加我们在开始时取回的值:

 .map(perm => [xs, ...perm]) // [[1, 2, 3], [1, 3, 2]]

now that is repeated for all the arrays elements and it results in: 现在,对所有数组元素重复,结果是:

 [
  // 1
  [[1, 2, 3], [1, 3, 2]],
  // 2
  [[2, 1, 3], [2, 3, 1]],
  // 3
  [[3, 1, 2], [3, 2, 1]]
]

now we just have to flatten(...) that array to get the desired result. 现在我们只需要flatten(...)该数组以获得所需的结果。

The whole thing could be expressed as a tree of recursive calls: 整个事情可以表示为递归调用树:

 [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]

I've delimited it a bit in order to add some console logs to debug it 为了添加一些控制台日志来调试它,我稍微划了一下

This can help of course. 这当然有帮助。 However keep in mind that simple recursive definitions can often result in complex execution traces. 但请记住,简单的递归定义通常会导致复杂的执行跟踪。

That is in fact one of reasons why recursion can be so useful. 这实际上是递归可能如此有用的原因之一。 Because some algorithms that have complicated iterations, admit a simple recursive description. 因为某些算法具有复杂的迭代,所以允许简单的递归描述。 So your goal in understanding a recursive algorithm should be to figure out the inductive (not iterative) reasoning in its definition. 因此,理解递归算法的目标应该是在其定义中找出归纳(而不是迭代)推理。

Lets forget about javascript and focus on the algorithm. 让我们忘记javascript并专注于算法。 Let's see we can obtain the permutations of elements of a set A , which we will denote P(A) . 让我们看看我们可以得到集合A的元素的排列,我们将表示P(A)

Note: It's of no relevance that in the original algorithm the input is a list, since the original order does not matter at all. 注意:在原始算法中输入是一个列表并不重要,因为原始顺序根本不重要。 Likewise it's of no relevance that we will return a set of lists rather than a list of lists, since we don't care the order in which solutions are calculated. 同样,我们将返回一组列表而不是列表列表,这是无关紧要的,因为我们不关心计算解决方案的顺序。

Base Case: 基本情况:

The simplest case is the empty set. 最简单的情况是空集。 There is exactly one solution for the permutations of 0 elements, and that solution is the empty sequence [] . 对于0个元素的排列,只有一个解决方案,该解决方案是空序列[] So, 所以,

P(A) = {[]}

Recursive Case: 递归案例:

In order to use recursion, you want to describe how to obtain P(A) from P(A') for some A' smaller than A in size. 为了使用递归,您想要描述如何从P(A')获得P(A)大小小于A某些A'

Note: If you do that, it's finished. 注意:如果你这样做,它就完成了。 Operationally the program will work out via successive calls to P with smaller and smaller arguments until it reaches the base case, and then it will come back bulding longer results from shorter ones. 在操作上,程序将通过对具有越来越小的参数的P连续调用来计算,直到它到达基本情况,然后它将从较短的结果中恢复较长的结果。

So here is one way to write a particular permutation of an A with n+1 elems. 所以这里有一种方法来编写一个具有n + 1个元素的A的特定排列。 You need to successively pick one element of A for each position: 您需要为每个位置连续选择A一个元素:

 _   _ ... _ 
n+1  n     1

So you pick an x ∈ A for the first 所以你为第一个选择一个x ∈ A

 x   _ ... _ 
     n     1

And then you need to choose a permutation in P(A\\{x}) . 然后你需要在P(A\\{x})选择一个排列。

This tells you one way to build all permutations of size n . 这告诉你一种构建大小为n所有排列的方法。 Consider all possible choices of x in A (to use as first element), and for each choice put x in front of each solution of P(A\\{x}) . 考虑的所有可能的选择xA (作为第一元件使用),并且对于每个选择放x中的每一溶液的前面P(A\\{x}) Finally take the union of all solutions you found for each choice of x . 最后,为每个x选择找到所有解决方案的联合。

Let's use the dot operator to represent putting x in front of a sequence s , and the diamond operator to represent putting x in front of every s ∈ S . 让我们使用点运算符来表示将x放在序列s前面,并使用菱形运算符来表示将x放在每个s ∈ S前面。 That is, 那是,

x⋅s = [x, s1, s2, ..., sn] 
x⟡S = {x⋅s : s ∈ S}

Then for a non-empty A 然后换一个非空A

P(A) = ⋃ {x⟡P(A\{x}) : x ∈ A} 

This expression together with the case base give you all the permutations of elements in a set A . 此表达式与案例库一起为您提供集合A中元素的所有排列。

The javascript code javascript代码

To understand how the code you've shown implements this algortithm you need to consider the following 要了解您显示的代码如何实现此算法,您需要考虑以下内容

  • That code considers two base cases, when you have 0 or 1 elements, by writing xs.length < 2 . 当你有0或1个元素时,该代码通过编写xs.length < 2考虑两个基本情况。 We could have done that too, it's irrelevant. 我们也可以这样做,这是无关紧要的。 You can change that 2 into a 1 and it should still work. 您可以将2更改为1,它仍然可以工作。

  • The mapping corresponds to our operation x⟡S = {x⋅s : s ∈ S} 映射对应于我们的运算x⟡S = {x⋅s : s ∈ S}

  • The without corresponds to P(A\\{x}) 不对应P(A\\{x})

  • The flatten corresponds to the which joins all solutions. flatten对应于连接所有解决方案的

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM