12

One more question about most elegant and simple implementation of element combinations in F#.

It should return all combinations of input elements (either List or Sequence). First argument is number of elements in a combination.

For example:

comb 2 [1;2;2;3];;
[[1;2]; [1;2]; [1;3]; [2;2]; [2;3]; [2;3]]
The_Ghost
  • 2,070
  • 15
  • 26
  • Vaguely related question: http://stackoverflow.com/questions/286427/calculating-permutations-in-f – Benjol Aug 03 '09 at 12:53

8 Answers8

13

One less concise and more faster solution than ssp:

let rec comb n l = 
    match n, l with
    | 0, _ -> [[]]
    | _, [] -> []
    | k, (x::xs) -> List.map ((@) [x]) (comb (k-1) xs) @ comb k xs
The_Ghost
  • 2,070
  • 15
  • 26
6
let rec comb n l =
  match (n,l) with
  | (0,_) -> [[]]
  | (_,[]) -> []
  | (n,x::xs) ->
      let useX = List.map (fun l -> x::l) (comb (n-1) xs)
      let noX = comb n xs
      useX @ noX
kvb
  • 54,864
  • 2
  • 91
  • 133
  • Fastest solution till now, but less concise. – The_Ghost Aug 05 '09 at 07:57
  • It looks very ugly in C#. public static IEnumerable> Combinations(IEnumerable xs,int n) { if(n == 0){ return new []{Enumerable.Empty()}; }else if(!xs.Any()){ return Enumerable.Empty>(); }else{ var head = xs.First(); var tail = xs.Skip(1); var useX = (Combinations(tail,n-1)).Select(l => (new[]{head}).Concat(l)); var noX = Combinations(tail,n); return useX.Concat(noX); } } – Rezo Megrelidze Jul 15 '14 at 10:19
1

There is more consise version of KVB's answer:

let rec comb n l =
  match (n,l) with
    | (0,_) -> [[]]
    | (_,[]) -> []
    | (n,x::xs) ->
      List.flatten [(List.map (fun l -> x::l) (comb (n-1) xs)); (comb n xs)]
ssp
  • 1,702
  • 9
  • 11
1

The accepted answer is gorgeous and quickly understandable if you are familiar with tree recursion. Since elegance was sought, opening this long dormant thread seems somewhat unnecessary.

However, a simpler solution was asked for. Iterative algorithms sometimes seem simpler to me. Furthermore, performance was mentioned as an indicator of quality, and iterative processes are sometimes faster than recursive ones.

The following code is tail recursive and generates an iterative process. It requires a third of the amount of time to compute combinations of size 12 from a list of 24 elements.

let combinations size aList = 
    let rec pairHeadAndTail acc bList = 
        match bList with
        | [] -> acc
        | x::xs -> pairHeadAndTail (List.Cons ((x,xs),acc)) xs
    let remainderAfter = aList |> pairHeadAndTail [] |> Map.ofList
    let rec comboIter n acc = 
        match n with
        | 0 -> acc
        | _ -> 
            acc
            |> List.fold (fun acc alreadyChosenElems ->
                match alreadyChosenElems with
                | [] -> aList //Nothing chosen yet, therefore everything remains.
                | lastChoice::_ -> remainderAfter.[lastChoice]
                |> List.fold (fun acc elem ->
                    List.Cons (List.Cons (elem,alreadyChosenElems),acc)
                ) acc
            ) []
            |> comboIter (n-1)
    comboIter size [[]]

The idea that permits an iterative process is to pre-compute a map of the last chosen element to a list of the remaining available elements. This map is stored in remainderAfter.

The code is not concise, nor does it conform to lyrical meter and rhyme.

1

A naive implementation using sequence expression. Personally I often feel sequence expressions are easier to follow than other more dense functions.

let combinations (k : int) (xs : 'a list) : ('a list) seq =
    let rec loop (k : int) (xs : 'a list) : ('a list) seq = seq {
        match xs with
        | [] -> ()
        | xs when k = 1 -> for x in xs do yield [x]
        | x::xs ->
            let k' = k - 1
            for ys in loop k' xs do
                yield x :: ys
            yield! loop k xs }
    loop k xs
    |> Seq.filter (List.length >> (=)k)
Michelrandahl
  • 3,365
  • 2
  • 26
  • 41
1

Method taken from Discrete Mathematics and Its Applications. The result returns an ordered list of combinations stored in arrays. And the index is 1-based.

let permutationA (currentSeq: int []) (n:int) (r:int): Unit = 
    let mutable i = r
    while currentSeq.[i - 1] = n - r + i do
        i <- (i - 1)
    currentSeq.[i - 1] <- currentSeq.[i - 1] + 1
    for j = i + 1 to r do
        currentSeq.[j - 1] <- currentSeq.[i - 1] + j - i   
    ()
let permutationNum (n:int) (r:int): int [] list =
    if n >= r then
        let endSeq = [|(n-r+1) .. n|]
        let currentSeq: int [] = [|1 .. r|]
        let mutable resultSet: int [] list = [Array.copy currentSeq];
        while currentSeq <> endSeq do
            permutationA currentSeq n r
            resultSet <- (Array.copy currentSeq) :: resultSet
        resultSet
    else
        []

This solution is simple and helper function costs constant memory.

Aria Ax
  • 168
  • 5
0

My solution is less concise, less effective (altho, no direct recursion used) but it trully returns all combinations (currently only pairs, need to extend filterOut so it can return a tuple of two lists, will do little later).

let comb lst =
    let combHelper el lst =
        lst |> List.map (fun lstEl -> el::[lstEl])
    let filterOut el lst =
        lst |> List.filter (fun lstEl -> lstEl <> el)
    lst |> List.map (fun lstEl -> combHelper lstEl (filterOut lstEl lst)) |> List.concat

comb [1;2;3;4] will return: [[1; 2]; [1; 3]; [1; 4]; [2; 1]; [2; 3]; [2; 4]; [3; 1]; [3; 2]; [3; 4]; [4; 1]; [4; 2]; [4; 3]]

Ray
  • 1,585
  • 9
  • 10
  • This solution is not working correctly. It doesn't return combinations, but only pairs of elements. – The_Ghost Aug 05 '09 at 07:45
  • It's all possible combinations. Not just tail combinations. comb [1;2;3] is 1 added to each of [2;3], 2 added to each of [1;3], 3 added to each of [1;2] – Ray Aug 05 '09 at 10:43
  • > comb 3 [1..4];; val it : int list list = [[1; 2; 3]; [1; 2; 4]; [1; 3; 4]; [2; 3; 4]] With more elements, it should not return pairs, but triples (for n=3) – The_Ghost Aug 05 '09 at 10:49
0

Ok, just tail combinations little different approach (without using of library function)

let rec comb n lst =
    let rec findChoices = function
      | h::t -> (h,t) :: [ for (x,l) in findChoices t -> (x,l) ]
      | []   -> []
    [ if n=0 then yield [] else
            for (e,r) in findChoices lst do
                for o in comb (n-1) r do yield e::o  ]
Ray
  • 1,585
  • 9
  • 10