Вычисление перестановок в F #

На основе этого вопроса и answer, как мне создать общий алгоритм перестановок в F #? Google не дает на этот вопрос никаких полезных ответов.

РЕДАКТИРОВАТЬ: я даю свой лучший ответ ниже, но я подозреваю, что у Томаса лучше (конечно, короче!)


person Benjol    schedule 13.11.2008    source источник


Ответы (7)


вы также можете написать что-то вроде этого:

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 }

Аргумент «список» содержит все числа, которые вы хотите переставить, а «принято» - это набор, который содержит уже использованные числа. Функция возвращает пустой список, когда все числа заняты. В противном случае он выполняет итерацию по всем числам, которые все еще доступны, получает все возможные перестановки оставшихся чисел (рекурсивно с использованием «перестановок») и добавляет текущее число к каждому из них перед возвратом (l :: perm).

Чтобы запустить это, вы дадите ему пустой набор, потому что в начале не используются числа:

permutations [1;2;3] Set.empty;;
person Tomas Petricek    schedule 13.11.2008
comment
К вашему сведению - Set.mem был переименован в Set.contains - person Stephen Swensen; 05.07.2010
comment
Похоже, ваше решение не допускает повторяющихся значений в исходном списке - person gmlion; 10.05.2019

Мне нравится эта реализация (но не могу вспомнить ее источник):

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))
person Johan Kullbom    schedule 02.02.2010
comment
Это выглядит действительно красиво. Можно ли преобразовать это в версию для различных перестановок? См. Мое собственное решение ниже, которое выглядит не так хорошо, как ваше. Спасибо. - person Emile; 23.08.2010
comment
Я бы хотел, чтобы ты запомнил источник. С точки зрения скорости, это превосходит все остальные функции перестановки, которые я пробовал. - person Rick Minerich; 27.04.2011
comment
@ rick-minerich Это почти идентично stackoverflow.com/questions/1526046/f -permutations /, хотя ИМО немного яснее ... - person Sergey Aldoukhov; 26.07.2011
comment
AFAIR, я видел эту реализацию около 10 лет назад во время моего визита в haskell -lands (и даже тогда она не была новинкой). Вероятно, истинный источник даже старше, это похоже на канонический материал колледжа. - person Miloslav Raus; 24.05.2016
comment
Также Seq.concat (Seq.map (insertions x) (permutations xs)) можно заменить на Seq.collect (insertions x) (permutations xs) - person STiLeTT; 21.12.2019

Решение Томаса довольно изящное: короткое, чисто функциональное и ленивое. Я думаю, это может быть даже хвостовая рекурсия. Кроме того, он производит перестановки лексикографически. Тем не менее, мы можем повысить производительность в два раза, используя императивное решение внутри компании, при этом открывая функциональный интерфейс извне.

Функция permutations принимает общую последовательность e, а также общую функцию сравнения f : ('a -> 'a -> int) и лениво выдает неизменяемые перестановки лексикографически. Функционал сравнения позволяет нам генерировать перестановки элементов, которые не обязательно comparable, а также легко задавать обратный или настраиваемый порядок.

Внутренняя функция permute - это императивная реализация алгоритма, описанного здесь. Функция преобразования let comparer f = { new System.Collections.Generic.IComparer<'a> with member self.Compare(x,y) = f x y } позволяет нам использовать перегрузку System.Array.Sort, которая выполняет пользовательскую сортировку поддиапазонов на месте с использованием 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' }

Теперь для удобства у нас есть где let flip f x y = f y x:

let permutationsAsc e = permutations compare e
let permutationsDesc e = permutations (flip compare) e
person Stephen Swensen    schedule 05.07.2010

Мой последний лучший ответ

//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

Функция перестановок работает путем построения n-арного дерева, представляющего все возможные перестановки переданного списка «вещей», а затем обхода дерева для построения списка списков. Использование Seq резко повышает производительность, поскольку делает все ленивым.

Второй параметр функции перестановок позволяет вызывающей стороне определить фильтр для «обрезки» дерева перед генерацией путей (см. Мой пример ниже, где мне не нужны начальные нули).

Некоторый пример использования: Node ‹'a> является общим, поэтому мы можем делать перестановки« что угодно »:

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] 

(Особая благодарность Томасу Петричеку, любому комментарии приветствуются)

person Benjol    schedule 13.11.2008
comment
Обратите внимание, что в F # есть функция List.permute, но она не делает то же самое (я не уверен, что она делает на самом деле ...) - person Benjol; 13.11.2008

Взгляните на это:

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))
person Holoed    schedule 14.04.2010

Если вам нужны разные перестановки (когда в исходном наборе есть дубликаты), вы можете использовать это:

let rec insertions pre c post =
    seq {
        if List.length post = 0 then
            yield pre @ [c]
        else
            if List.forall (fun x->x<>c) post then
                yield pre@[c]@post
            yield! insertions (pre@[post.Head]) c post.Tail
        }

let rec permutations l =
    seq {
        if List.length l = 1 then
            yield l
        else
            let subperms = permutations l.Tail
            for sub in subperms do
                yield! insertions [] l.Head sub
        }

Это прямой перевод этого Код C #. Я открыт для предложений по более функциональному внешнему виду.

person Emile    schedule 23.08.2010

Если вам нужны перестановки с повторениями, это подход «по книге» с использованием List.indexed вместо сравнения элементов для фильтрации элементов при построении перестановки.

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
person gmlion    schedule 10.05.2019