Прежде всего, старайтесь по возможности избегать конкатенации списков (@), так как это O (N) вместо O (1).
Я бы начал с (относительно) простого для понимания плана вычисления декартового внешнего произведения списков.
- Добавить каждый элемент первого списка к каждому подсписку в декартовом произведении остальных списков.
- Позаботьтесь о базовом случае.
Первая версия:
let rec cartesian = function
| [] -> [[]]
| L::Ls -> [for C in cartesian Ls do yield! [for x in L do yield x::C]]
Это прямой перевод приведенных выше предложений в код.
Теперь ускорите это: вместо списков используйте списки и карты:
let rec cartesian2 = function
| [] -> [[]]
| L::Ls -> cartesian2 Ls |> List.collect (fun C -> L |> List.map (fun x->x::C))
Это можно сделать еще быстрее, вычисляя списки по запросу с помощью последовательности:
let rec cartesian3 = function
| [] -> Seq.singleton []
| L::Ls -> cartesian3 Ls |> Seq.collect (fun C -> L |> Seq.map (fun x->x::C))
Эта последняя форма - то, что я использую сам, так как мне чаще всего нужно просто перебирать результаты, а не иметь их все сразу.
Некоторые тесты на моей машине:
Тестовый код:
let test f N =
let fss0 = List.init N (fun i -> List.init (i+1) (fun j -> j+i*i+i))
f fss0 |> Seq.length
Результаты в FSI:
> test projection 10;;
Real: 00:00:18.066, CPU: 00:00:18.062, GC gen0: 168, gen1: 157, gen2: 7
val it : int = 3628800
> test cartesian 10;;
Real: 00:00:19.822, CPU: 00:00:19.828, GC gen0: 244, gen1: 121, gen2: 3
val it : int = 3628800
> test cartesian2 10;;
Real: 00:00:09.247, CPU: 00:00:09.250, GC gen0: 94, gen1: 52, gen2: 2
val it : int = 3628800
> test cartesian3 10;;
Real: 00:00:04.254, CPU: 00:00:04.250, GC gen0: 359, gen1: 1, gen2: 0
val it : int = 3628800