Solving N choose M problem, the functional approach

分析

在长度为N的列表L里,选出M个元素的组合。
也就是N choose M的问题。
如果L满足H|T形式,则N choose M from L的组合种类等于:
使用了H的所有组合 加上 完全不使用H的组合,即:

(N choose M from L) = (N-1 choose M-1 from T) + (N-1 choose M from T)
  • (N-1 choose M-1 from T) 就是使用了H(列表首元素)的所有组合,可以这样思考:
    因为使用了H所以只用从剩下的N-1选M-1的组合,然后再依次和H拼装,就可以组成完整地一对组合。
  • (N-1 choose M from T)就好理解了,即完全排除H以后的所有组合方式。注意这个就是更小规模的同类问题。

这样我们就把一个问题归约成一个规模更小的同类问题,使用递归方法即可求解!


举个栗子

现在要从[a, b, c, d]四个字母里选出两个,这个问题被分解成:
1. 从[b,c,d]里选1个(满足递归出口M==1)=>[b,c,d]。拼装a*[b,c,d]=>(a b),(a c),(a d)
2. 从[b,c,d]里选2个(递归了)
最后合并1,2的结果得到 (a b),(a c),(a d),(b c),(b d),(c d)这既是从abcd选2个的全组合。


代码

因为在Edx上跟着Peter Van Roy学编程范式,所以就用课里的玩具OZ语言来写了,概念和其他函数式语言一样,语法也简单易懂,主要是表达个思路,转换成其他语言应该不是什么难事。

根据前面的分析中的式子,很快就可以写出Comb函数

declare
fun {Comb L N M}
  case L of nil then nil
  [] H|T then
    if N==M then [L]
    elseif M==1 then L
    else
    {AppendList {ExpandList H {Comb T N-1 M-1}} {Comb T N-1 M}}
    end
  else nil end
end

然后定义两个辅助方法,其中AppendList是把两个List合并起来,[a b c]+[d e f]=[a b c d e f]
ExpandList用于把一个元素分别分配给一个List里的所有元素,a*[a b c]=[[a a] [a b] [a c]]

declare
fun {AppendList L1 L2}
  case L1 of nil then L2
  [] H|T then H|{AppendList T L2} end
end

declare
fun {ExpandList A L}
  case L of H|T then
    [A H]|{ExpandList A T}
  else L end
end

定义好这些方法后我们可以运行{Browse {Comb [a b c d] 4 3}}来看看从abcd四个字母里选3个的组合结果:

[[a [b c]] [a [b d]] [a [c d]] [b c d]]

输出List的每个元素确实包含了完整的一项组合,结果是正确的,只是嵌套得太丑了,需要改进。
让我们遍历这个List,取出每个元素做一个扁平化处理吧:p

declare
fun {Beautify L A}
  case L of H|T then
    {Beautify T {Flatten H}|A}
  else A end
end

declare
fun {Flatten L}
  case L of nil then nil
  [] H|T then
    {AppendList {Flatten H} {Flatten T}}
  else [L] end
end

最后让我们调用{Browse {Beautify {Comb [a b c d] 4 3}}}看看美化后的结果:

[[b c d] [a c d] [a b d] [a b c]]

完成。

你可能感兴趣的:(算法,函数式编程,组合)