由于cnblogs的代码着色系统不支持erlang,所以就直接从博客上贴过来了,如果大家看的不习惯的话,就直接来我的博客上看吧
本文章为本人个人博客相应文章的镜像:
原文地址: http://www.greatony.com/index.php/2010/03/18/a-combination-algorithm-written-in-erlang/
在上一篇博客中,我用Erlang实现了一个优惠计算器,其中的核心一块内容就是组合算法的实现。
在这里组合的概念呢,是指,一些数的全部组合方式。举个例子来说:现在有这些数[1,2,3],那么他们一共有几种组合方式呢?
- 组合成1个组
- 组合成2个组
- [1,2], [3]
- [1, 3], [2]
- [2,3], [1]
- 组合成3个组
所以一共有6种方式。
所以,我们的问题就是,现在有一个列表List,我们需要找出List中的元素的所有组合,该如何写这个算法呢?
我们从我们习惯的数学的角度出发:
- 对于一个空的列表[],它没有任何组合方式(我们规定[[]]不是一个合法的组合)
- 对于包含一个元素的列表[Head],它只有一种组合方式:[[Head]]
- 对于包含两个及以上元素的列表,我们记为[Head|Rest],它的组合方式的数量取决于Rest中所有组合的数量和组数
这里,对于第3点需要再详细的解释一下:
假设Rest为[1,2,3,4,5],它的一种组合为如下形式:
[[1], [2,3], [4,5]]
而Head为6,那么[6,1,2,3,4,5]就具有(不仅具有)如下几种组合方式:
- [[1,6],[2,3],[4,5]]
- [[1],[2,3,6],[4,5]]
- [[1],[2,3],[4,5,6]]
- [[1],[2,3],[4,5],[6]]
也就说,如果将Rest分为m组,那么对于每种组合,[Head|List]就有相应的(m+1)种组合。
对于长度为n的list,它应该有几种组合方式,我们可以放下不管(因为我们要的是效果)。
根据上面的推理,我们可以将第3种情况细化成如下的描述:
- 如果List包含2个及以上的元素,可以表示为[Head|Rest]的形式,那么对于Rest中的每一种组合,我们可以将Head分别放入这个组合中的每一个分组或新的一个分组中,得到Rest在这个排列下,[Head|Rest]的所有组合。
好了,思路整理清楚了,我们就可以开始写代码了:
首先,我们写一个函数(我称之为reduce_comb)来根据Rest的一种组合Comb以及Head来找到对应的[Head|Rest]的所有组合:
01 | reduce_comb( Comb , Head ) -> reduce_comb_inner([], Comb , Head ); |
04 | reduce_comb_inner( Prefix , [], Element ) -> [ Prefix ++ [[ Element ]]]; |
06 | reduce_comb_inner( Prefix , [ Head | Rest ], Element ) -> |
08 | Prefix ++ [[ Element | Head ]] ++ Rest |
09 | | reduce_comb_inner( Prefix ++ [ Head ], Rest , Element ) |
好了,这个大问题解决了,现在解决小问题,就是把上面写好的那三点逻辑写出来:
02 | combination(_ Function , Default , []) -> Default ; |
04 | combination( Function , Default , [ Head ]) -> Function ([[ Head ]], Default ); |
06 | combination( Function , Default , [ Head | List ]) -> |
07 | combination( fun ( Combination , OldSolution ) -> |
08 | ReducedCombination = reduce_comb( Combination , Head ), |
09 | lists:foldl ( Function , old Solution , ReducedCombination ) |