Принятый ответ великолепен и быстро понятен, если вы знакомы с древовидной рекурсией. Поскольку требовалась элегантность, открытие этой длинной спящей нити кажется несколько ненужным.
Однако было предложено более простое решение. Итерационные алгоритмы иногда кажутся мне проще. Кроме того, производительность упоминалась как показатель качества, и итерационные процессы иногда выполняются быстрее, чем рекурсивные.
Следующий код является хвостовой рекурсией и генерирует итерационный процесс. Для вычисления комбинаций размера 12 из списка из 24 элементов требуется треть времени.
let combinations size aList =
let rec pairHeadAndTail acc bList =
match bList with
| [] -> acc
| x::xs -> pairHeadAndTail (List.Cons ((x,xs),acc)) xs
let remainderAfter = aList |> pairHeadAndTail [] |> Map.ofList
let rec comboIter n acc =
match n with
| 0 -> acc
| _ ->
acc
|> List.fold (fun acc alreadyChosenElems ->
match alreadyChosenElems with
| [] -> aList //Nothing chosen yet, therefore everything remains.
| lastChoice::_ -> remainderAfter.[lastChoice]
|> List.fold (fun acc elem ->
List.Cons (List.Cons (elem,alreadyChosenElems),acc)
) acc
) []
|> comboIter (n-1)
comboIter size [[]]
Идея, которая допускает итеративный процесс, состоит в том, чтобы предварительно вычислить карту последнего выбранного элемента в списке оставшихся доступных элементов. Эта карта хранится в remainderAfter
.
Код не является кратким и не соответствует лирическому метру и рифме.