Haskell版本(1.03s):
module Main where
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Control.Monad
import Control.Applicative ((<$>))
import Data.Vector.Unboxed (Vector,(!))
import qualified Data.Vector.Unboxed as V
solve :: Vector Int -> Int
solve ar =
V.foldl' go 0 ar' where
ar' = V.zip ar (V.postscanr' max 0 ar)
go sr (p,m) = sr + m - p
main = do
t <- fmap (read . T.unpack) TIO.getLine -- With Data.Text, the example finishes 15% faster.
T.unlines . map (T.pack . show . solve . V.fromList . map (read . T.unpack) . T.words)
<$> replicateM t (TIO.getLine >> TIO.getLine) >>= TIO.putStr
F#版本(0.17s):
open System
let solve (ar : uint64[]) =
let ar' =
let t = Array.scanBack max ar 0UL |> fun x -> Array.take (x.Length-1) x
Array.zip ar t
let go sr (p,m) = sr + m - p
Array.fold go 0UL ar'
let getIntLine() =
Console.In.ReadLine().Split [|' '|]
|> Array.choose (fun x -> if x <> "" then uint64 x |> Some else None)
let getInt() = getIntLine().[0]
let t = getInt()
for i=1 to int t do
getInt() |> ignore
let ar = getIntLine()
printfn "%i" (solve ar)
以上两个程序是Stock Maximize problem的解决方案,时间是Run Code
按钮的第一个测试用例。
出于某种原因,F#版本大约快6倍,但我很确定如果我用强制循环替换慢库函数,我可以将其加速至少3倍,更可能是10倍。
Haskell版本可以同样改进吗?
我正在进行上述操作以进行学习,总的来说,我发现很难弄清楚如何编写有效的Haskell代码。
答案 0 :(得分:75)
如果切换到ByteString
并坚持使用普通的Haskell列表(而不是向量),您将获得更有效的解决方案。您也可以使用单个左侧折叠重写求解函数,然后绕过zip并右扫描(1)。
总的来说,在我的机器上,与 Haskell 解决方案(2)相比,我获得了20倍的性能提升。
在Haskell下面,代码的执行速度比F#代码快:
import Data.List (unfoldr)
import Control.Applicative ((<$>))
import Control.Monad (replicateM_)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C
parse :: ByteString -> [Int]
parse = unfoldr $ C.readInt . C.dropWhile (== ' ')
solve :: [Int] -> Int
solve xs = foldl go (const 0) xs minBound
where go f x s = if s < x then f x else s - x + f s
main = do
[n] <- parse <$> B.getLine
replicateM_ n $ B.getLine >> B.getLine >>= print . solve . parse
<子> 1。有关使用solve
和zip
实现scanr
的此答案的早期版本,请参阅edits。
<子> 2。 HackerRank网站甚至显示出更大的性能提升。
答案 1 :(得分:54)
如果我想在F#中快速执行此操作,我会避免solve
中的所有高阶函数,并编写一个C风格的命令循环:
let solve (ar : uint64[]) =
let mutable sr, m = 0UL, 0UL
for i in ar.Length-1 .. -1 .. 0 do
let p = ar.[i]
m <- max p m
sr <- sr + m - p
sr
根据我的测量结果,这比你的F#快11倍。
然后性能受到IO层(unicode解析)和字符串拆分的限制。这可以通过读入字节缓冲区并手动编写词法分析器来优化:
let buf = Array.create 65536 0uy
let mutable idx = 0
let mutable length = 0
do
use stream = System.Console.OpenStandardInput()
let rec read m =
let c =
if idx < length then
idx <- idx + 1
else
length <- stream.Read(buf, 0, buf.Length)
idx <- 1
buf.[idx-1]
if length > 0 && '0'B <= c && c <= '9'B then
read (10UL * m + uint64(c - '0'B))
else
m
let read() = read 0UL
for _ in 1UL .. read() do
Array.init (read() |> int) (fun _ -> read())
|> solve
|> System.Console.WriteLine
答案 2 :(得分:45)
仅供记录,F#版本也不是最佳选择。我认为此时并不重要,但如果人们想要比较性能,那么值得注意的是它可以更快。
我没有非常努力(你可以通过使用限制性突变来加快速度,这不会违反F#的性质),但使用Seq
而不是Array
进行简单的更改在正确的位置(以避免分配临时数组)使代码快2到3倍:
let solve (ar : uint64[]) =
let ar' = Seq.zip ar (Array.scanBack max ar 0UL)
let go sr (p,m) = sr + m - p
Seq.fold go 0UL ar'
如果您使用Seq.zip
,也可以删除take
来电(因为Seq.zip
会自动截断序列)。使用以下代码段使用#time
进行衡量:
let rnd = Random()
let inp = Array.init 100000 (fun _ -> uint64 (rnd.Next()))
for a in 0 .. 10 do ignore (solve inp) // Measure this line
原始代码大约需要150ms,使用新版本大约需要50-75ms。