Проблема в том, что функция добавления не является хвостовой рекурсивной. Каждая рекурсия использует немного стекового пространства для хранения своего состояния, и когда список увеличивается, функция добавления занимает все больше и больше стекового пространства. В какой-то момент стек просто недостаточно велик и код не работает.
Как вы предложили в вопросе, способ избежать этого - использовать хвостовую рекурсию. При работе со списками это обычно означает построение списков в обратном порядке. Функция добавления становится просто ::
.
Если порядок результирующего списка важен, список необходимо поменять местами в конце. Так что нередко видеть код, возвращающий List.rev acc
. Это занимает O (n) времени, но постоянное пространство и является хвостовой рекурсивной. Так что стеку нет предела.
Таким образом, ваш код станет:
let rec gen size l =
if size = 0 then
List.rev l
else
let n = Random.int 1000000 in
let list = n :: l in
gen (size - 1) list
Еще несколько вещей для оптимизации:
При построении результата побитово через рекурсию результатом обычно являются имена acc
, сокращенно от аккумулятора, и передаются первыми:
let rec gen acc size =
if size = 0 then
List.rev acc
else
let n = Random.int 1000000 in
let list = n :: acc in
gen list (size - 1)
Это позволяет затем использовать функцию и сопоставление с образцом вместо аргумента размера, и если конструкция:
let rec gen acc = function
| 0 -> List.rev acc
| size ->
let n = Random.int 1000000 in
let list = n :: acc in
gen list (size - 1)
Список случайных чисел обычно так же хорошо перевернут. Если вам не нужны списки разных размеров, но с одним и тем же начальным числом для начала с одинаковой последовательности чисел, вы можете пропустить List.rev. И n :: acc
такая простая структура, которую обычно не связывают с переменной.
let rec gen acc = function
| 0 -> acc
| size ->
let n = Random.int 1000000 in
gen (n :: acc) (size - 1)
И, наконец, вы можете воспользоваться дополнительными аргументами. Хотя это делает код более сложным для чтения, он значительно упрощает его использование:
let rec gen ?(acc=[]) = function
| 0 -> acc
| size ->
let n = Random.int 1000000 in
gen ~acc:(n :: acc) (size - 1)
# gen 5;;
- : int list = [180439; 831641; 180182; 326685; 809344]
Вам больше не нужно указывать пустой список для генерации списка случайных чисел.
Примечание. Альтернативным способом является использование функции-оболочки:
let gen size =
let rec loop acc = function
| 0 -> acc
| size ->
let n = Random.int 1000000 in
loop (n :: acc) (size - 1)
in loop [] size