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

时间:2018-11-14 17:56:46

标签: 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]]

来自Permutations in JavaScript? 由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]]

有人可以逐步向我展示引擎盖下发生了什么吗?

2 个答案:

答案 0 :(得分:2)

要获取所有置换,请执行以下操作:

我们从左到右获取数组的一个元素。

 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]

答案 1 :(得分:1)

  

为了添加一些控制台日志进行调试,我对它进行了一些界定

这当然可以提供帮助。但是请记住,简单的递归定义通常会导致复杂的执行跟踪。

这实际上是递归如此有用的原因之一。由于某些算法的迭代复杂,因此请接受简单的递归描述。因此,您理解递归算法的目标应该是找出其定义中的归纳(而非迭代)推理。

让我们忘掉javascript并专注于算法。让我们看看我们可以获得集合A的元素的排列,我们将其表示为P(A)

注意:与原始算法中的输入为列表无关,因为原始顺序根本不重要。同样,我们将返回一组列表而不是列表列表也没有关系,因为我们不在乎解决方案的计算顺序。

基本案例:

最简单的情况是空集。对于0个元素的排列,只有一个解决方案,而该解决方案是空序列[]。因此,

P(A) = {[]}

递归案例:

为了使用递归,您想描述如何从P(A)中获取一些P(A')小于A'的{​​{1}}的方法。

注意:如果执行此操作,则说明已完成。从操作上讲,该程序将通过连续调用A并使用越来越小的参数,直到达到基本情况为止,然后从较短的结果中恢复出更长的结果。

因此,这是一种编写带有n + 1个元素的P的特定排列的方法。您需要为每个位置依次选择A的一个元素:

A

因此,您为第一个选择 _ _ ... _ n+1 n 1

x ∈ A

然后您需要在 x _ ... _ n 1 中选择一个排列。

这告诉您一种构建大小为P(A\{x})的所有排列的方法。考虑nx的所有可能选择(用作第一个元素),对于每个选择,将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} 中元素的所有排列。

JavaScript代码

要了解显示的代码如何实现该算法,您需要考虑以下内容

  • 当您具有0或1个元素时,该代码通过编写A来考虑两种基本情况。我们也可以这样做,这无关紧要。您可以将2更改为1,并且仍然可以使用。

  • 映射对应于我们的操作xs.length < 2

  • with对应于x⟡S = {x⋅s : s ∈ S}

  • 展平对应于连接所有解决方案的P(A\{x})