Calculating permutations in F#

后端 未结 7 1772
别跟我提以往
别跟我提以往 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:26

    My latest best answer

    //mini-extension to List for removing 1 element from a list
    module List = 
        let remove n lst = List.filter (fun x -> x <> n) lst
    
    //Node type declared outside permutations function allows us to define a pruning filter
    type Node<'a> =
        | Branch of ('a * Node<'a> seq)
        | Leaf of 'a
    
    let permutations treefilter lst =
        //Builds a tree representing all possible permutations
        let rec nodeBuilder lst x = //x is the next element to use
            match lst with  //lst is all the remaining elements to be permuted
            | [x] -> seq { yield Leaf(x) }  //only x left in list -> we are at a leaf
            | h ->   //anything else left -> we are at a branch, recurse 
                let ilst = List.remove x lst   //get new list without i, use this to build subnodes of branch
                seq { yield Branch(x, Seq.map_concat (nodeBuilder ilst) ilst) }
    
        //converts a tree to a list for each leafpath
        let rec pathBuilder pth n = // pth is the accumulated path, n is the current node
            match n with
            | Leaf(i) -> seq { yield List.rev (i :: pth) } //path list is constructed from root to leaf, so have to reverse it
            | Branch(i, nodes) -> Seq.map_concat (pathBuilder (i :: pth)) nodes
    
        let nodes = 
            lst                                     //using input list
            |> Seq.map_concat (nodeBuilder lst)     //build permutations tree
            |> Seq.choose treefilter                //prune tree if necessary
            |> Seq.map_concat (pathBuilder [])      //convert to seq of path lists
    
        nodes
    

    The permutations function works by constructing an n-ary tree representing all possible permutations of the list of 'things' passed in, then traversing the tree to construct a list of lists. Using 'Seq' dramatically improves performance as it makes everything lazy.

    The second parameter of the permutations function allows the caller to define a filter for 'pruning' the tree before generating the paths (see my example below, where I don't want any leading zeros).

    Some example usage: Node<'a> is generic, so we can do permutations of 'anything':

    let myfilter n = Some(n)  //i.e., don't filter
    permutations myfilter ['A';'B';'C';'D'] 
    
    //in this case, I want to 'prune' leading zeros from my list before generating paths
    let noLeadingZero n = 
        match n with
        | Branch(0, _) -> None
        | n -> Some(n)
    
    //Curry myself an int-list permutations function with no leading zeros
    let noLZperm = permutations noLeadingZero
    noLZperm [0..9] 
    

    (Special thanks to Tomas Petricek, any comments welcome)

    0 讨论(0)
  • 2020-11-30 08:28

    I like this implementation (but can't remember the source of it):

    let rec insertions x = function
        | []             -> [[x]]
        | (y :: ys) as l -> (x::l)::(List.map (fun x -> y::x) (insertions x ys))
    
    let rec permutations = function
        | []      -> seq [ [] ]
        | x :: xs -> Seq.concat (Seq.map (insertions x) (permutations xs))
    
    0 讨论(0)
  • 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
    
    0 讨论(0)
  • 2020-11-30 08:40

    you can also write something like this:

    let rec permutations list taken = 
      seq { if Set.count taken = List.length list then yield [] else
            for l in list do
              if not (Set.contains l taken) then 
                for perm in permutations list (Set.add l taken)  do
                  yield l::perm }
    

    The 'list' argument contains all the numbers that you want to permute and 'taken' is a set that contains numbers already used. The function returns empty list when all numbers all taken. Otherwise, it iterates over all numbers that are still available, gets all possible permutations of the remaining numbers (recursively using 'permutations') and appends the current number to each of them before returning (l::perm).

    To run this, you'll give it an empty set, because no numbers are used at the beginning:

    permutations [1;2;3] Set.empty;;
    
    0 讨论(0)
  • 2020-11-30 08:42

    If you need permutations with repetitions, this is the "by the book" approach using List.indexed instead of element comparison to filter out elements while constructing a permutation.

    let permutations s =
        let rec perm perms carry rem =
            match rem with
                | [] -> carry::perms
                | l ->
                    let li = List.indexed l
                    let permutations =
                            seq { for ci in li ->
                                    let (i, c) = ci
                                    (perm
                                            perms
                                            (c::carry)
                                            (li |> List.filter (fun (index, _) -> i <> index) |> List.map (fun (_, char) -> char))) }
    
                    permutations |> Seq.fold List.append []
        perm [] [] s
    
    0 讨论(0)
  • 2020-11-30 08:44

    Take a look at this one:

    http://fsharpcode.blogspot.com/2010/04/permutations.html

    let length = Seq.length
    let take = Seq.take
    let skip = Seq.skip
    let (++) = Seq.append
    let concat = Seq.concat
    let map = Seq.map
    
    let (|Empty|Cons|) (xs:seq<'a>) : Choice<Unit, 'a * seq<'a>> =
        if (Seq.isEmpty xs) then Empty else Cons(Seq.head xs, Seq.skip 1 xs)
    
    let interleave x ys =
        seq { for i in [0..length ys] ->
                (take i ys) ++ seq [x] ++ (skip i ys) }
    
    let rec permutations xs =
                match xs with
                | Empty -> seq [seq []]
                | Cons(x,xs) -> concat(map (interleave x) (permutations xs))
    
    0 讨论(0)
提交回复
热议问题