Calculating permutations in F#

后端 未结 7 1774
别跟我提以往
别跟我提以往 2020-11-30 07:46

Inspired by this question and answer, how do I create a generic permutations algorithm in F#? Google doesn\'t give any useful answers to this.

EDIT: I provide my be

7条回答
  •  [愿得一人]
    2020-11-30 08:36

    Tomas' solution is quite elegant: it's short, purely functional, and lazy. I think it may even be tail-recursive. Also, it produces permutations lexicographically. However, we can improve performance two-fold using an imperative solution internally while still exposing a functional interface externally.

    The function permutations takes a generic sequence e as well as a generic comparison function f : ('a -> 'a -> int) and lazily yields immutable permutations lexicographically. The comparison functional allows us to generate permutations of elements which are not necessarily comparable as well as easily specify reverse or custom orderings.

    The inner function permute is the imperative implementation of the algorithm described here. The conversion function let comparer f = { new System.Collections.Generic.IComparer<'a> with member self.Compare(x,y) = f x y } allows us to use the System.Array.Sort overload which does in-place sub-range custom sorts using an IComparer.

    let permutations f e =
        ///Advances (mutating) perm to the next lexical permutation.
        let permute (perm:'a[]) (f: 'a->'a->int) (comparer:System.Collections.Generic.IComparer<'a>) : bool =
            try
                //Find the longest "tail" that is ordered in decreasing order ((s+1)..perm.Length-1).
                //will throw an index out of bounds exception if perm is the last permuation,
                //but will not corrupt perm.
                let rec find i =
                    if (f perm.[i] perm.[i-1]) >= 0 then i-1
                    else find (i-1)
                let s = find (perm.Length-1)
                let s' = perm.[s]
    
                //Change the number just before the tail (s') 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] s') > 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] <- s'
    
                //Sort the tail in increasing order.
                System.Array.Sort(perm, s+1, perm.Length - s - 1, comparer)
                true
            with
            | _ -> false
    
        //permuation sequence expression 
        let c = f |> comparer
        let freeze arr = arr |> Array.copy |> Seq.readonly
        seq { let e' = Seq.toArray e
              yield freeze e'
              while permute e' f c do
                  yield freeze e' }
    

    Now for convenience we have the following where let flip f x y = f y x:

    let permutationsAsc e = permutations compare e
    let permutationsDesc e = permutations (flip compare) e
    

提交回复
热议问题