Calculating permutations in F#

后端 未结 7 1792
别跟我提以往
别跟我提以往 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)

提交回复
热议问题