Как указывает Дейв, проблема в том, что вы используете оператор @
для добавления списков. Это более значительная проблема производительности, чем хвостовая рекурсия. Фактически, хвостовая рекурсия на самом деле не слишком ускоряет программу (но она заставляет ее работать на больших входах, где стек переполняется).
Причина, по которой ваша вторая версия медленнее, заключается в том, что вы добавляете более короткий список (созданный с помощью [...]
) в более длинный список (accumList
). Это медленнее, чем добавление более длинного списка в более короткий список (потому что операция должна копировать первый список).
Вы можете исправить это, собрав элементы в аккумуляторе в обратном порядке, а затем поменяв их местами, прежде чем вернуть результат:
let indicesForStepTail start blockSize maxSize =
let rec indicesForStepInternal istart accumList =
match istart with
| i when i > maxSize -> accumList |> List.rev
| _ ->
let acc =
[for j in ((min (istart + blockSize) maxSize) - 1) .. -1 .. istart -> j]
@ accumList
indicesForStepInternal (istart + 2 * blockSize) acc
indicesForStepInternal start []
Как вы можете видеть, этот список имеет более короткий список (сгенерированный с использованием [...]
) в качестве первого аргумента @
, и на моей машине он имеет производительность, аналогичную нерекурсивно-рекурсивной версии. Обратите внимание, что понимание [ ... ]
генерирует элементы в обратном порядке - так что они могут быть обращены обратно в конце.
Вы также можете написать все более красиво, используя синтаксис F # seq { .. }
. Вы можете полностью избежать использования оператора @
, поскольку он позволяет вам выдавать отдельные элементы, используя yield
, и выполнять хвостово-рекурсивные вызовы, используя yield!
:
let rec indicesForStepSeq start blockSize maxSize = seq {
match start with
| i when i > maxSize -> ()
| _ ->
for j in start .. ((min (start + blockSize) maxSize) - 1) do
yield j
yield! indicesForStepSeq (start + 2 * blockSize) blockSize maxSize }
Вот как бы я это написал. При вызове вам просто нужно добавить Seq.toList
, чтобы оценить всю ленивую последовательность. Производительность этой версии аналогична первой.
РЕДАКТИРОВАТЬ С поправкой от Daniel, версия Seq
на самом деле немного быстрее!