我一直在尝试以下Haskell代码:
data Foo = Foo
{ fooMin :: Float
, fooMax :: Float
, fooSum :: Float
} deriving Show
getLocalFoo :: [Float] -> Foo
getLocalFoo x = Foo a b c
where
a = minimum x
b = maximum x
c = sum x
getGlobalFoo :: [Foo] -> Foo
getGlobalFoo x = Foo a b c
where
a = minimum $ fmap fooMin x
b = maximum $ fmap fooMax x
c = sum $ fmap fooSum x
main :: IO()
main = do
let numItems = 2000
let numLists = 100000
putStrLn $ "numItems: " ++ show numItems
putStrLn $ "numLists: " ++ show numLists
-- Create an infinite list of lists of floats, x is [[Float]]
let x = take numLists $ repeat [1.0 .. numItems]
-- Print two first elements of each item
print $ take 2 (map (take 2) x)
-- First calculate local min/max/sum for each float list
-- then calculate the global min/max/sum based on the results.
print . getGlobalFoo $ fmap getLocalFoo x
在调整numItems和numLists时按顺序测试运行时:
小尺寸
numItems: 4.0
numLists: 2
[[1.0,2.0],[1.0,2.0]]
Foo {fooMin = 1.0, fooMax = 4.0, fooSum = 20.0}
real 0m0.005s
user 0m0.004s
sys 0m0.001s
大尺寸
numItems: 2000.0
numLists: 100000
[[1.0,2.0],[1.0,2.0]]
Foo {fooMin = 1.0, fooMax = 2000.0, fooSum = 1.9999036e11}
real 0m33.116s
user 0m33.005s
sys 0m0.109s
我用直觉和幼稚的方式编写了这段代码而不考虑性能,但是我担心这远远不是最佳代码,因为我实际上可能需要多次折叠列表然后需要?
有人可以建议更好地实施此测试吗?
答案 0 :(得分:2)
使用foldl
库可以一次有效地运行多个折叠。实际上,它非常擅长于您不需要将列表拆分为子列表。您可以将所有列表连接成一个巨大的列表并直接折叠。
以下是:
import Control.Applicative
import qualified Control.Foldl as L
data Foo = Foo
{ fooMin :: Maybe Float
, fooMax :: Maybe Float
, fooSum :: Float
} deriving Show
foldFloats :: L.Fold Float Foo
foldFloats = Foo <$> L.minimum <*> L.maximum <*> L.sum
-- or: foldFloats = liftA3 Foo L.minimum L.maximum L.sum
main :: IO()
main = do
let numItems = 2000
let numLists = 100000
putStrLn $ "numItems: " ++ show numItems
putStrLn $ "numLists: " ++ show numLists
-- Create an infinite list of lists of floats, x is [[Float]]
let x = replicate numLists [1.0 .. numItems]
-- Print two first elements of each item
print $ take 2 (map (take 2) x)
print $ L.fold foldFloats (concat x)
与您的代码的主要区别是:
我使用replicate n
,这与take n . repeat
相同。事实上,这就是replicate
实际定义的方式
我不打算单独处理子列表。我只是concat
将它们全部放在一起并一次折叠。
我使用Maybe
作为最小值和最大值,因为我需要处理空列表的情况。
此代码更快
以下是数字:
$ time ./fold
numItems: 2000.0
numLists: 100000
[[1.0,2.0],[1.0,2.0]]
Foo {fooMin = Just 1.0, fooMax = Just 2000.0, fooSum = 3.435974e10}
real 0m5.796s
user 0m5.756s
sys 0m0.024s
foldl
是一个非常小且易于学习的库。您可以详细了解here。
答案 1 :(得分:1)
Option
中,因为我们需要以某种方式表示空集合的最小值和最大值。 (另一种方法是将自己限制为非空集合,然后我们可以使用半群而不是幺半群。)
我们需要的另一件事是确保在每一步中强制执行所有计算。为此,我们声明Foo
的{{1}}实例,添加我们使用的monoid类型的一些缺失实例,以及在折叠操作期间强制值的辅助函数。
NFData
答案 2 :(得分:1)
也许单个折叠更便宜。尝试运行一些类似的测试:
{-# LANGUAGE BangPatterns #-}
import Data.List
getLocalFoo :: [Float] -> Foo
getLocalFoo [] = error "getLocalFoo: empty list"
getLocalFoo (x:xs) = foldl' f (Foo x x x) xs
where f (Foo !min1 !max1 !sum1) y =
Foo (min1 `min` y) (max1 `max` y) (sum1 + y)
与getGlobalFoo
类似。