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)
上記の2つのプログラムは ストック最大化問題 のソリューションであり、時間はRun Code
ボタンの最初のテストケースのものです。
何らかの理由でF#バージョンは約6倍高速ですが、遅いライブラリ関数を命令型ループに置き換えた場合、少なくとも3倍、おそらく10倍高速化できると確信しています。
Haskellバージョンも同様に改善できますか?
私は学習目的で上記を行っており、一般的に、効率的なHaskellコードの書き方を見つけるのは難しいと感じています。
ByteString
に切り替えて、(ベクトルではなく)単純なHaskellリストを使用すると、より効率的なソリューションが得られます。 単一の左折りで解決関数を書き換え、Zipと右スキャンをバイパスすることもできます (1)。全体的に、私のマシンでは、Haskellソリューションと比較してパフォーマンスが20倍向上しています (2)。
以下の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 Webサイトでは、さらに大きなパフォーマンスの改善が示されています。
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レイヤー(ユニコード解析)および文字列分割によって制限されます。これは、バイトバッファーに読み取り、レクサーを手動で書き込むことによって最適化できます。
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
記録のためだけに、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
新しいバージョンを使用すると、元のコードで約150ミリ秒、50〜75ミリ秒で取得できます。