Функциональный стиль F# гораздо медленнее
Попытка выучить F#, решая некоторые задачи программирования. Я не хочу добавлять слишком много подробностей о проблеме, так как я не хочу портить удовольствие другим.
По сути, проблема состоит в том, чтобы найти все 4-uples { (i,j,k,l) | i ^ j ^ k ^ l!= 0 } без повторений (например, (1,1,1,2) и (1,1,2,1) одинаковы и должны учитываться только один раз).
Я нашел подход O(n^3), который работает, пожалуйста, смотрите countImperative(a,b,c,d)
ниже. Но я также попытался изменить код, чтобы избавиться от вложенных циклов for. Однако я не смог бы сделать это без существенного снижения производительности. У меня сложилось впечатление, что синтаксический сахар F# позволит использовать более лаконичный стиль (с использованием конвейеров и сгибов), позволяя компилятору выполнять тяжелую работу для создания сравнительно быстрого кода (по сравнению с моим вложенным циклом). Большой удар по производительности происходит из расчета partial2
сумма.
Вот код:
open System
open System.Diagnostics
open System.Collections
module quadruples =
[<EntryPoint>]
let main argv =
let input = "2000 2000 2000 2000"
let ordered = [ for x in input.Split([|' '|]) -> Convert.ToInt32(x) ] |> List.sort
let a,b,c,d = ordered.[0], ordered.[1], ordered.[2], ordered.[3]
let inner(a,b) = a * (a-1) / 2 + a * (b-a)
let sw = new Stopwatch()
sw.Start()
let partial1 = [ 1.. b ] |> List.fold (fun acc j -> acc + (int64 ((min a j) * inner(c-j+1, d-j+1)))) 0L
sw.Stop()
let elapsed1 = (sw.ElapsedMilliseconds |> double) / 1000.0
printfn "Partial1: %f s" elapsed1
sw.Restart()
let combinations = [ for i in 1..a do for j in i+1..b do yield (j,i^^^j) ]
let range = [ 1..c ]
let partial2 = combinations |> List.fold(fun acc (j,x) -> acc + (range |> List.skip(j-1) |> List.fold(fun acc k -> if k ^^^ x < k || k ^^^ x > d then acc + 1L else acc) 0L)) 0L
sw.Stop()
let elapsed2 = (sw.ElapsedMilliseconds |> double) / 1000.0
printfn "Partial2: %f s" elapsed2
printfn "Functional: %d, Elapsed: %f s" (partial1 + partial2) (elapsed1 + elapsed2)
// "imperative" approach
let countImperative(a,b,c,d) =
let mutable count = seq { 1..b } |> Seq.fold (fun acc j -> acc + (int64 ((min a j) * inner(c-j+1, d-j+1)))) 0L
for i in 1..a do
for j in i+1..b do
let x = i ^^^ j
for k in j..c do
let y = x ^^^ k
if y < k || y > d then
count <- count + 1L
count
sw.Restart();
let count = countImperative(a,b,c,d)
sw.Stop()
printfn "Imperative: %d, Elapsed: %f s" count ((sw.ElapsedMilliseconds |> double) / 1000.0)
0 // return an integer exit code
Поэтому мой вопрос был, есть ли способ ускорить код (в частности, расчет partial2
), сохраняя хороший синтаксис F#.