Решение Томаса довольно элегантно: оно короткое, чисто функциональное и ленивое. Я думаю, что это может быть даже хвостовой рекурсии. Кроме того, он производит перестановки лексикографически. Тем не менее, мы можем повысить производительность в два раза, используя внутреннее императивное решение, в то же время внешне открывая функциональный интерфейс.
Функция 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