Combinations and Permutations in F#

后端 未结 3 941
北荒
北荒 2020-12-13 11:23

I\'ve recently written the following combinations and permutations functions for an F# project, but I\'m quite aware they\'re far from optimised.

/// Rotates         


        
相关标签:
3条回答
  • 2020-12-13 11:47

    I noticed that your updated getPerms function contains duplicates. Here's my crack at a dupe-free version. Hopefully the comments speak for themselves. The hardest part was writing an efficient distrib function, because the concatenation operator has to be used somewhere. Luckily it's only used on small sublists, so the performance remains reasonable. My getAllPerms code below generates all permutations of [1..9] in around a quarter of a second, all 10-element permutations in around 2.5 seconds.

    Edit: funny, I didn't look at Tomas' code, but his combinations function and my picks function are nearly identical.

    // All ordered picks {x_i1, x_i2, .. , x_ik} of k out of n elements {x_1,..,x_n}
    // where i1 < i2 < .. < ik
    let picks n L = 
        let rec aux nleft acc L = seq {
            match nleft,L with
            | 0,_ -> yield acc
            | _,[] -> ()
            | nleft,h::t -> yield! aux (nleft-1) (h::acc) t
                            yield! aux nleft acc t }
        aux n [] L
    
    // Distribute an element y over a list:
    // {x1,..,xn} --> {y,x1,..,xn}, {x1,y,x2,..,xn}, .. , {x1,..,xn,y}
    let distrib y L =
        let rec aux pre post = seq {
            match post with
            | [] -> yield (L @ [y])
            | h::t -> yield (pre @ y::post)
                      yield! aux (pre @ [h]) t }
        aux [] L
    
    // All permutations of a single list = the head of a list distributed
    // over all permutations of its tail
    let rec getAllPerms = function
        | [] -> Seq.singleton []
        | h::t -> getAllPerms t |> Seq.collect (distrib h)
    
    // All k-element permutations out of n elements = 
    // all permutations of all ordered picks of length k combined
    let getPerms2 n lst = picks n lst |> Seq.collect getAllPerms
    

    Edit: more code in response to comments

    // Generates the cartesian outer product of a list of sequences LL
    let rec outerProduct = function
        | [] -> Seq.singleton []
        | L::Ls -> L |> Seq.collect (fun x -> 
                    outerProduct Ls |> Seq.map (fun L -> x::L))
    
    // Generates all n-element combination from a list L
    let getPermsWithRep2 n L = 
        List.replicate n L |> outerProduct  
    
    0 讨论(0)
  • 2020-12-13 11:47

    If you want to write efficient functional code, then it is a good idea to avoid using the @ operator, because concatentation of lists is very inefficient.

    Here is an example of how you can write a function to generate all combinations:

    let rec combinations acc size set = seq {
      match size, set with 
      | n, x::xs -> 
          if n > 0 then yield! combinations (x::acc) (n - 1) xs
          if n >= 0 then yield! combinations acc n xs 
      | 0, [] -> yield acc 
      | _, [] -> () }
    
    combinations [] 3 [1 .. 4]
    

    The parameters of the function are:

    • acc is used to remember elements that are already selected to be included in the combination (initially this is an empty list)
    • size is the remaining number of elements that we need to add to acc (initially this is the required size of the combinations)
    • set is the set elements to choose from

    The function is implemented using a simple recursion. If we need to generate combinations of size n then we can either add or don't add the current element, so we try to generate combinations using both options (first case) and add all of them to the generated sequence using yield!. If we need 0 more elements, then we successfuly generated a combination (second case) and if we end with some other number but don't have any remaining elements to use then we cannot return anything (last case).

    Combinations with repetition would be similar - the difference is that you don't need to remove the elements from the list (by using just xs in the recursive calls) so there are more options of what to do.

    0 讨论(0)
  • 2020-12-13 12:05

    If you have a real need for speed, I encourage you to first find the fastest algorithm for your problem and if the algorithm turns out to be inherently imperative (e.g. bubble sort or the Sieve of Eratosthenes), by all means, use F#'s imperative features for your implementation internally while keeping your API pure for library consumers (more work and risk for you, but excellent results for library consumers).

    Specific to your question, I've adapted my fast implementation for generating all permutations of a set lexicographically (originally presented here) to generate r-length permutations:

    open System
    open System.Collections.Generic
    
    let flip f x y = f y x
    
    ///Convert the given function to an IComparer<'a>
    let comparer f = { new IComparer<_> with member self.Compare(x,y) = f x y }
    
    ///generate r-length lexicographical permutations of e using the comparison function f.
    ///permutations start with e and continue until the last lexicographical permutation of e:
    ///if you want all permuations for a given set, make sure to order e before callings this function.
    let lexPerms f r e =
        if r < 0 || r > (Seq.length e) then
            invalidArg "e" "out of bounds" |> raise
    
        //only need to compute IComparers used for Array.Sort in-place sub-range overload once
        let fComparer = f |> comparer
        let revfComparer = f |> flip |> comparer
    
        ///Advances (mutating) perm to the next lexical permutation.
        let lexPermute perm =
            //sort last perm.Length - r elements in decreasing order,
            //thereby avoiding duplicate permutations of the first r elements
            //todo: experiment with eliminate this trick and instead concat all
            //lex perms generated from ordered combinations of length r of e (like cfern)
            Array.Sort(perm, r, Array.length perm - r, revfComparer)
    
            //Find the index, call it s, just before the longest "tail" that is
            //ordered  in decreasing order ((s+1)..perm.Length-1).
            let rec tryFind i =
                if i = 0 then
                    None
                elif (f perm.[i] perm.[i-1]) >= 0 then
                    Some(i-1)
                else
                    tryFind (i-1)
    
            match tryFind (perm.Length-1) with
            | Some s ->
                let sValue = perm.[s]
    
                //Change the value just before the tail (sValue) to the
                //smallest number bigger than it in the tail (perm.[t]).
                let rec find i imin =
                    if i = perm.Length then
                        imin
                    elif (f perm.[i] sValue) > 0 && (f perm.[i] perm.[imin]) < 0 then
                        find (i+1) i
                    else
                        find (i+1) imin
    
                let t = find (s+1) (s+1)
    
                perm.[s] <- perm.[t]
                perm.[t] <- sValue
    
                //Sort the tail in increasing order.
                Array.Sort(perm, s+1, perm.Length - s - 1, fComparer)
                true
            | None ->
                false
    
        //yield copies of each perm
        seq {
            let e' = Seq.toArray e
            yield e'.[..r-1]
            while lexPermute e' do
                yield e'.[..r-1]
        }
    
    let lexPermsAsc r e = lexPerms compare r e
    let lexPermsDesc r e = lexPerms (flip compare) r e
    

    I am not sure if adapting this algorithm to r-length permutations is terribly inappropriate (i.e. whether there are better imperative or functional algorithms specifically for this problem), but it does, on average, perform almost twice as fast as your latest getPerms implementation for the set [1;2;3;4;5;6;7;8;9], and has the additional feature of yielding the r-length permutations lexicographically (notice also with interest how lexPermsAsc is not monotonic as a function of r):

    r       lexPermsAsc(s)  getPerms(s)
    1       0.002           0.002
    2       0.004           0.002
    3       0.019           0.007
    4       0.064           0.014
    5       0.264           0.05
    6       0.595           0.307
    7       1.276           0.8
    8       1.116           2.247
    9       1.107           4.235
    avg.:   0.494           0.852
    
    0 讨论(0)
提交回复
热议问题